Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/report

This commit is contained in:
dohyeons 2025-10-02 18:01:14 +09:00
commit 57c4e8317d
183 changed files with 2927 additions and 1109 deletions

View File

@ -178,14 +178,19 @@ export class MailAccountFileController {
try { try {
const { id } = req.params; const { id } = req.params;
// TODO: 실제 SMTP 연결 테스트 구현 const account = await mailAccountFileService.getAccountById(id);
// const account = await mailAccountFileService.getAccountById(id); if (!account) {
// nodemailer로 연결 테스트 return res.status(404).json({
success: false,
message: '계정을 찾을 수 없습니다.',
});
}
return res.json({ // mailSendSimpleService의 testConnection 사용
success: true, const { mailSendSimpleService } = require('../services/mailSendSimpleService');
message: '연결 테스트 성공 (미구현)', const result = await mailSendSimpleService.testConnection(id);
});
return res.json(result);
} catch (error: unknown) { } catch (error: unknown) {
const err = error as Error; const err = error as Error;
return res.status(500).json({ return res.status(500).json({

View File

@ -7,10 +7,12 @@ export class MailSendSimpleController {
*/ */
async sendMail(req: Request, res: Response) { async sendMail(req: Request, res: Response) {
try { try {
console.log('📧 메일 발송 요청 수신:', { accountId: req.body.accountId, to: req.body.to, subject: req.body.subject });
const { accountId, templateId, to, subject, variables, customHtml } = req.body; const { accountId, templateId, to, subject, variables, customHtml } = req.body;
// 필수 파라미터 검증 // 필수 파라미터 검증
if (!accountId || !to || !Array.isArray(to) || to.length === 0) { if (!accountId || !to || !Array.isArray(to) || to.length === 0) {
console.log('❌ 필수 파라미터 누락');
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: '계정 ID와 수신자 이메일이 필요합니다.', message: '계정 ID와 수신자 이메일이 필요합니다.',

View File

@ -1,8 +1,12 @@
import { Router } from 'express'; import { Router } from 'express';
import { mailAccountFileController } from '../controllers/mailAccountFileController'; import { mailAccountFileController } from '../controllers/mailAccountFileController';
import { authenticateToken } from '../middleware/authMiddleware';
const router = Router(); const router = Router();
// 모든 메일 계정 라우트에 인증 미들웨어 적용
router.use(authenticateToken);
router.get('/', (req, res) => mailAccountFileController.getAllAccounts(req, res)); router.get('/', (req, res) => mailAccountFileController.getAllAccounts(req, res));
router.get('/:id', (req, res) => mailAccountFileController.getAccountById(req, res)); router.get('/:id', (req, res) => mailAccountFileController.getAccountById(req, res));
router.post('/', (req, res) => mailAccountFileController.createAccount(req, res)); router.post('/', (req, res) => mailAccountFileController.createAccount(req, res));

View File

@ -4,8 +4,12 @@
import express from 'express'; import express from 'express';
import { MailReceiveBasicController } from '../controllers/mailReceiveBasicController'; import { MailReceiveBasicController } from '../controllers/mailReceiveBasicController';
import { authenticateToken } from '../middleware/authMiddleware';
const router = express.Router(); const router = express.Router();
// 모든 메일 수신 라우트에 인증 미들웨어 적용
router.use(authenticateToken);
const controller = new MailReceiveBasicController(); const controller = new MailReceiveBasicController();
// 메일 목록 조회 // 메일 목록 조회

View File

@ -1,8 +1,12 @@
import { Router } from 'express'; import { Router } from 'express';
import { mailSendSimpleController } from '../controllers/mailSendSimpleController'; import { mailSendSimpleController } from '../controllers/mailSendSimpleController';
import { authenticateToken } from '../middleware/authMiddleware';
const router = Router(); const router = Router();
// 모든 메일 발송 라우트에 인증 미들웨어 적용
router.use(authenticateToken);
// POST /api/mail/send/simple - 메일 발송 // POST /api/mail/send/simple - 메일 발송
router.post('/simple', (req, res) => mailSendSimpleController.sendMail(req, res)); router.post('/simple', (req, res) => mailSendSimpleController.sendMail(req, res));

View File

@ -1,8 +1,12 @@
import { Router } from 'express'; import { Router } from 'express';
import { mailTemplateFileController } from '../controllers/mailTemplateFileController'; import { mailTemplateFileController } from '../controllers/mailTemplateFileController';
import { authenticateToken } from '../middleware/authMiddleware';
const router = Router(); const router = Router();
// 모든 메일 템플릿 라우트에 인증 미들웨어 적용
router.use(authenticateToken);
// 템플릿 CRUD // 템플릿 CRUD
router.get('/', (req, res) => mailTemplateFileController.getAllTemplates(req, res)); router.get('/', (req, res) => mailTemplateFileController.getAllTemplates(req, res));
router.get('/:id', (req, res) => mailTemplateFileController.getTemplateById(req, res)); router.get('/:id', (req, res) => mailTemplateFileController.getTemplateById(req, res));

View File

@ -6,6 +6,7 @@
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import { mailAccountFileService } from './mailAccountFileService'; import { mailAccountFileService } from './mailAccountFileService';
import { mailTemplateFileService } from './mailTemplateFileService'; import { mailTemplateFileService } from './mailTemplateFileService';
import { encryptionService } from './encryptionService';
export interface SendMailRequest { export interface SendMailRequest {
accountId: string; accountId: string;
@ -56,18 +57,39 @@ class MailSendSimpleService {
throw new Error('메일 내용이 없습니다.'); throw new Error('메일 내용이 없습니다.');
} }
// 4. SMTP 연결 생성 // 4. 비밀번호 복호화
const decryptedPassword = encryptionService.decrypt(account.smtpPassword);
console.log('🔐 비밀번호 복호화 완료');
console.log('🔐 암호화된 비밀번호 (일부):', account.smtpPassword.substring(0, 30) + '...');
console.log('🔐 복호화된 비밀번호 길이:', decryptedPassword.length);
// 5. SMTP 연결 생성
// 포트 465는 SSL/TLS를 사용해야 함
const isSecure = account.smtpPort === 465 ? true : (account.smtpSecure || false);
console.log('📧 SMTP 연결 설정:', {
host: account.smtpHost,
port: account.smtpPort,
secure: isSecure,
user: account.smtpUsername,
});
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({
host: account.smtpHost, host: account.smtpHost,
port: account.smtpPort, port: account.smtpPort,
secure: account.smtpSecure, // SSL/TLS secure: isSecure, // SSL/TLS (포트 465는 자동으로 true)
auth: { auth: {
user: account.smtpUsername, user: account.smtpUsername,
pass: account.smtpPassword, pass: decryptedPassword, // 복호화된 비밀번호 사용
}, },
// 타임아웃 설정 (30초)
connectionTimeout: 30000,
greetingTimeout: 30000,
}); });
// 5. 메일 발송 console.log('📧 메일 발송 시도 중...');
// 6. 메일 발송
const info = await transporter.sendMail({ const info = await transporter.sendMail({
from: `"${account.name}" <${account.email}>`, from: `"${account.name}" <${account.email}>`,
to: request.to.join(', '), to: request.to.join(', '),
@ -75,6 +97,12 @@ class MailSendSimpleService {
html: htmlContent, html: htmlContent,
}); });
console.log('✅ 메일 발송 성공:', {
messageId: info.messageId,
accepted: info.accepted,
rejected: info.rejected,
});
return { return {
success: true, success: true,
messageId: info.messageId, messageId: info.messageId,
@ -83,6 +111,8 @@ class MailSendSimpleService {
}; };
} catch (error) { } catch (error) {
const err = error as Error; const err = error as Error;
console.error('❌ 메일 발송 실패:', err.message);
console.error('❌ 에러 상세:', err);
return { return {
success: false, success: false,
error: err.message, error: err.message,
@ -178,22 +208,42 @@ class MailSendSimpleService {
*/ */
async testConnection(accountId: string): Promise<{ success: boolean; message: string }> { async testConnection(accountId: string): Promise<{ success: boolean; message: string }> {
try { try {
console.log('🔌 SMTP 연결 테스트 시작:', accountId);
const account = await mailAccountFileService.getAccountById(accountId); const account = await mailAccountFileService.getAccountById(accountId);
if (!account) { if (!account) {
throw new Error('계정을 찾을 수 없습니다.'); throw new Error('계정을 찾을 수 없습니다.');
} }
// 비밀번호 복호화
const decryptedPassword = encryptionService.decrypt(account.smtpPassword);
console.log('🔐 비밀번호 복호화 완료');
// 포트 465는 SSL/TLS를 사용해야 함
const isSecure = account.smtpPort === 465 ? true : (account.smtpSecure || false);
console.log('🔌 SMTP 연결 설정:', {
host: account.smtpHost,
port: account.smtpPort,
secure: isSecure,
user: account.smtpUsername,
});
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({
host: account.smtpHost, host: account.smtpHost,
port: account.smtpPort, port: account.smtpPort,
secure: account.smtpSecure, secure: isSecure,
auth: { auth: {
user: account.smtpUsername, user: account.smtpUsername,
pass: account.smtpPassword, pass: decryptedPassword, // 복호화된 비밀번호 사용
}, },
connectionTimeout: 10000, // 10초 타임아웃
greetingTimeout: 10000,
}); });
console.log('🔌 SMTP 연결 검증 중...');
await transporter.verify(); await transporter.verify();
console.log('✅ SMTP 연결 검증 성공!');
return { return {
success: true, success: true,
@ -201,6 +251,7 @@ class MailSendSimpleService {
}; };
} catch (error) { } catch (error) {
const err = error as Error; const err = error as Error;
console.error('❌ SMTP 연결 실패:', err.message);
return { return {
success: false, success: false,
message: `연결 실패: ${err.message}`, message: `연결 실패: ${err.message}`,

View File

@ -0,0 +1,43 @@
# syntax=docker/dockerfile:1
# Base image (Debian-based for glibc + OpenSSL compatibility)
FROM node:20-bookworm-slim AS base
WORKDIR /app
ENV NODE_ENV=production
# Install OpenSSL, curl (for healthcheck), and required certs
RUN apt-get update \
&& apt-get install -y --no-install-recommends openssl ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
# Dependencies stage (install production dependencies)
FROM base AS deps
COPY package*.json ./
RUN npm ci --omit=dev --prefer-offline --no-audit && npm cache clean --force
# Build stage (compile TypeScript)
FROM node:20-bookworm-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --prefer-offline --no-audit && npm cache clean --force
COPY tsconfig.json ./
COPY src ./src
RUN npm run build
# Runtime image
FROM base AS runner
ENV NODE_ENV=production
# Copy production node_modules
COPY --from=deps /app/node_modules ./node_modules
# Copy built files
COPY --from=build /app/dist ./dist
# Copy package files
COPY package*.json ./
# Create logs and uploads directories and set permissions (use existing node user with UID 1000)
RUN mkdir -p logs uploads && chown -R node:node logs uploads && chmod -R 755 logs uploads
EXPOSE 3001
USER node
CMD ["node", "dist/app.js"]

View File

@ -0,0 +1,60 @@
version: "3.8"
services:
# Node.js 백엔드
backend:
build:
context: ../../backend-node
dockerfile: ../docker/deploy/backend.Dockerfile
container_name: pms-backend-prod
restart: always
environment:
NODE_ENV: production
PORT: "3001"
HOST: 0.0.0.0
DATABASE_URL: postgresql://postgres:ph0909!!@39.117.244.52:11132/plm
JWT_SECRET: ilshin-plm-super-secret-jwt-key-2024
JWT_EXPIRES_IN: 24h
CORS_ORIGIN: https://v1.vexplor.com
CORS_CREDENTIALS: "true"
LOG_LEVEL: info
ENCRYPTION_KEY: ilshin-plm-mail-encryption-key-32characters-2024-secure
volumes:
- /home/vexplor/backend_data:/app/uploads
labels:
- traefik.enable=true
- traefik.http.routers.backend.rule=Host(`api.vexplor.com`)
- traefik.http.routers.backend.entrypoints=websecure,web
- traefik.http.routers.backend.tls=true
- traefik.http.routers.backend.tls.certresolver=le
- traefik.http.services.backend.loadbalancer.server.port=3001
# Next.js 프론트엔드
frontend:
build:
context: ../../frontend
dockerfile: ../docker/deploy/frontend.Dockerfile
args:
- NEXT_PUBLIC_API_URL=https://api.vexplor.com/api
container_name: pms-frontend-prod
restart: always
environment:
NODE_ENV: production
NEXT_PUBLIC_API_URL: https://api.vexplor.com/api
NEXT_TELEMETRY_DISABLED: "1"
PORT: "3000"
HOSTNAME: 0.0.0.0
volumes:
- /home/vexplor/frontend_data:/app/data
labels:
- traefik.enable=true
- traefik.http.routers.frontend.rule=Host(`v1.vexplor.com`)
- traefik.http.routers.frontend.entrypoints=websecure,web
- traefik.http.routers.frontend.tls=true
- traefik.http.routers.frontend.tls.certresolver=le
- traefik.http.services.frontend.loadbalancer.server.port=3000
networks:
default:
name: toktork_server_default
external: true

View File

@ -0,0 +1,59 @@
# Multi-stage build for Next.js
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies
COPY package.json package-lock.json* ./
RUN npm install
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Disable telemetry during the build
ENV NEXT_TELEMETRY_DISABLED 1
# 빌드 시 환경변수 설정 (ARG로 받아서 ENV로 설정)
ARG NEXT_PUBLIC_API_URL=https://api.vexplor.com/api
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
# Build the application
ENV DISABLE_ESLINT_PLUGIN=true
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy the Next.js build output
COPY --from=builder /app/public ./public
# Production 모드에서는 .next 폴더 전체를 복사
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
# node_modules 복사 (production dependencies)
COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
# Next.js start 명령어 사용
CMD ["npm", "start"]

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ import {
createMailAccount, createMailAccount,
updateMailAccount, updateMailAccount,
deleteMailAccount, deleteMailAccount,
testMailAccountConnection,
CreateMailAccountDto, CreateMailAccountDto,
UpdateMailAccountDto, UpdateMailAccountDto,
} from "@/lib/api/mail"; } from "@/lib/api/mail";
@ -104,6 +105,24 @@ export default function MailAccountsPage() {
} }
}; };
const handleTestConnection = async (account: MailAccount) => {
try {
setLoading(true);
const result = await testMailAccountConnection(account.id);
if (result.success) {
alert(`✅ SMTP 연결 성공!\n\n${result.message || '정상적으로 연결되었습니다.'}`);
} else {
alert(`❌ SMTP 연결 실패\n\n${result.message || '연결에 실패했습니다.'}`);
}
} catch (error: any) {
console.error('연결 테스트 실패:', error);
alert(`❌ SMTP 연결 테스트 실패\n\n${error.message || '알 수 없는 오류가 발생했습니다.'}`);
} finally {
setLoading(false);
}
};
return ( return (
<div className="min-h-screen bg-gray-50"> <div className="min-h-screen bg-gray-50">
<div className="w-full max-w-none px-4 py-8 space-y-8"> <div className="w-full max-w-none px-4 py-8 space-y-8">
@ -148,6 +167,7 @@ export default function MailAccountsPage() {
onEdit={handleOpenEditModal} onEdit={handleOpenEditModal}
onDelete={handleOpenDeleteModal} onDelete={handleOpenDeleteModal}
onToggleStatus={handleToggleStatus} onToggleStatus={handleToggleStatus}
onTestConnection={handleTestConnection}
/> />
</CardContent> </CardContent>
</Card> </Card>

View File

@ -14,6 +14,7 @@ import {
Calendar, Calendar,
Clock Clock
} from "lucide-react"; } from "lucide-react";
import { getMailAccounts, getMailTemplates } from "@/lib/api/mail";
interface DashboardStats { interface DashboardStats {
totalAccounts: number; totalAccounts: number;
@ -38,17 +39,15 @@ export default function MailDashboardPage() {
const loadStats = async () => { const loadStats = async () => {
setLoading(true); setLoading(true);
try { try {
// 계정 수 // 계정 수 (apiClient를 통해 토큰 포함)
const accountsRes = await fetch('/api/mail/accounts'); const accounts = await getMailAccounts();
const accountsData = await accountsRes.json();
// 템플릿 수 // 템플릿 수 (apiClient를 통해 토큰 포함)
const templatesRes = await fetch('/api/mail/templates-file'); const templates = await getMailTemplates();
const templatesData = await templatesRes.json();
setStats({ setStats({
totalAccounts: accountsData.success ? accountsData.data.length : 0, totalAccounts: accounts.length,
totalTemplates: templatesData.success ? templatesData.data.length : 0, totalTemplates: templates.length,
sentToday: 0, // TODO: 실제 발송 통계 API 연동 sentToday: 0, // TODO: 실제 발송 통계 API 연동
receivedToday: 0, receivedToday: 0,
sentThisMonth: 0, sentThisMonth: 0,

View File

@ -83,7 +83,7 @@ export default function ScreenManagementPage() {
<div className="space-y-8"> <div className="space-y-8">
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-4"> <div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-4">
<h2 className="text-xl font-semibold text-gray-800">{stepConfig.list.title}</h2> <h2 className="text-xl font-semibold text-gray-800">{stepConfig.list.title}</h2>
<Button className="bg-blue-600 hover:bg-blue-700 shadow-sm" onClick={() => goToNextStep("design")}> <Button variant="default" className="shadow-sm" onClick={() => goToNextStep("design")}>
<ArrowRight className="ml-2 h-4 w-4" /> <ArrowRight className="ml-2 h-4 w-4" />
</Button> </Button>
</div> </div>
@ -121,7 +121,7 @@ export default function ScreenManagementPage() {
<ArrowLeft className="mr-2 h-4 w-4" /> <ArrowLeft className="mr-2 h-4 w-4" />
</Button> </Button>
<Button className="bg-blue-600 hover:bg-blue-700 shadow-sm" onClick={() => goToStep("list")}> <Button variant="default" className="shadow-sm" onClick={() => goToStep("list")}>
</Button> </Button>
</div> </div>

View File

@ -54,7 +54,7 @@ const TEST_COMPONENTS: ComponentData[] = [
required: true, required: true,
style: { style: {
labelFontSize: "14px", labelFontSize: "14px",
labelColor: "#3b83f6", labelColor: "#212121",
labelFontWeight: "500", labelFontWeight: "500",
}, },
} as WidgetComponent, } as WidgetComponent,
@ -72,7 +72,7 @@ const TEST_COMPONENTS: ComponentData[] = [
required: true, required: true,
style: { style: {
labelFontSize: "14px", labelFontSize: "14px",
labelColor: "#3b83f6", labelColor: "#212121",
labelFontWeight: "500", labelFontWeight: "500",
}, },
} as WidgetComponent, } as WidgetComponent,
@ -94,7 +94,7 @@ const TEST_COMPONENTS: ComponentData[] = [
}, },
style: { style: {
labelFontSize: "14px", labelFontSize: "14px",
labelColor: "#3b83f6", labelColor: "#212121",
labelFontWeight: "500", labelFontWeight: "500",
}, },
} as WidgetComponent, } as WidgetComponent,
@ -112,7 +112,7 @@ const TEST_COMPONENTS: ComponentData[] = [
required: false, required: false,
style: { style: {
labelFontSize: "14px", labelFontSize: "14px",
labelColor: "#3b83f6", labelColor: "#212121",
labelFontWeight: "500", labelFontWeight: "500",
}, },
} as WidgetComponent, } as WidgetComponent,
@ -130,7 +130,7 @@ const TEST_COMPONENTS: ComponentData[] = [
required: false, required: false,
style: { style: {
labelFontSize: "14px", labelFontSize: "14px",
labelColor: "#3b83f6", labelColor: "#212121",
labelFontWeight: "500", labelFontWeight: "500",
}, },
} as WidgetComponent, } as WidgetComponent,
@ -152,7 +152,7 @@ const TEST_COMPONENTS: ComponentData[] = [
}, },
style: { style: {
labelFontSize: "14px", labelFontSize: "14px",
labelColor: "#3b83f6", labelColor: "#212121",
labelFontWeight: "500", labelFontWeight: "500",
}, },
} as WidgetComponent, } as WidgetComponent,

View File

@ -9,13 +9,13 @@ import { Badge } from "@/components/ui/badge";
*/ */
export default function MainPage() { export default function MainPage() {
return ( return (
<div className="pt-10 space-y-6"> <div className="space-y-6 pt-10">
{/* 메인 컨텐츠 */} {/* 메인 컨텐츠 */}
{/* Welcome Message */} {/* Welcome Message */}
<Card> <Card>
<CardContent className="pt-6"> <CardContent className="pt-6">
<div className="space-y-6 text-center"> <div className="space-y-6 text-center">
<h3 className="text-lg font-semibold">PLM !</h3> <h3 className="text-lg font-semibold">Vexolor !</h3>
<p className="text-muted-foreground"> .</p> <p className="text-muted-foreground"> .</p>
<div className="flex justify-center space-x-2"> <div className="flex justify-center space-x-2">
<Badge variant="secondary">Spring Boot</Badge> <Badge variant="secondary">Spring Boot</Badge>

View File

@ -148,7 +148,7 @@ export default function ScreenViewPage() {
const screenHeight = layout?.screenResolution?.height || 800; const screenHeight = layout?.screenResolution?.height || 800;
return ( return (
<div className="h-full w-full overflow-auto bg-gradient-to-br from-gray-50 to-slate-100 pt-10"> <div className="h-full w-full overflow-auto bg-gradient-to-br from-gray-50 to-slate-100 p-10">
{layout && layout.components.length > 0 ? ( {layout && layout.components.length > 0 ? (
// 캔버스 컴포넌트들을 정확한 해상도로 표시 // 캔버스 컴포넌트들을 정확한 해상도로 표시
<div <div
@ -158,7 +158,6 @@ export default function ScreenViewPage() {
height: `${screenHeight}px`, height: `${screenHeight}px`,
minWidth: `${screenWidth}px`, minWidth: `${screenWidth}px`,
minHeight: `${screenHeight}px`, minHeight: `${screenHeight}px`,
margin: "0 auto 40px auto", // 하단 여백 추가
}} }}
> >
{layout.components {layout.components
@ -239,7 +238,7 @@ export default function ScreenViewPage() {
const labelText = component.style?.labelText || component.label || ""; const labelText = component.style?.labelText || component.label || "";
const labelStyle = { const labelStyle = {
fontSize: component.style?.labelFontSize || "14px", fontSize: component.style?.labelFontSize || "14px",
color: component.style?.labelColor || "#3b83f6", color: component.style?.labelColor || "#212121",
fontWeight: component.style?.labelFontWeight || "500", fontWeight: component.style?.labelFontWeight || "500",
backgroundColor: component.style?.labelBackgroundColor || "transparent", backgroundColor: component.style?.labelBackgroundColor || "transparent",
padding: component.style?.labelPadding || "0", padding: component.style?.labelPadding || "0",
@ -379,7 +378,7 @@ export default function ScreenViewPage() {
) : ( ) : (
// 빈 화면일 때도 깔끔하게 표시 // 빈 화면일 때도 깔끔하게 표시
<div <div
className="mx-auto flex items-center justify-center bg-white" className="mx-auto flex items-center justify-center bg-white rounded-xl border border-gray-200/60 shadow-lg shadow-gray-900/5"
style={{ style={{
width: `${screenWidth}px`, width: `${screenWidth}px`,
height: `${screenHeight}px`, height: `${screenHeight}px`,

View File

@ -56,10 +56,10 @@ export const GlobalFileViewer: React.FC<GlobalFileViewerProps> = ({
// 파일 아이콘 가져오기 // 파일 아이콘 가져오기
const getFileIcon = (fileName: string, size: number = 16) => { const getFileIcon = (fileName: string, size: number = 16) => {
const extension = fileName.split('.').pop()?.toLowerCase() || ''; const extension = fileName.split('.').pop()?.toLowerCase() || '';
const iconProps = { size, className: "text-gray-600" }; const iconProps = { size, className: "text-muted-foreground" };
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(extension)) { if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(extension)) {
return <Image {...iconProps} className="text-blue-600" />; return <Image {...iconProps} className="text-primary" />;
} }
if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm'].includes(extension)) { if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm'].includes(extension)) {
return <Video {...iconProps} className="text-purple-600" />; return <Video {...iconProps} className="text-purple-600" />;
@ -71,7 +71,7 @@ export const GlobalFileViewer: React.FC<GlobalFileViewerProps> = ({
return <Archive {...iconProps} className="text-yellow-600" />; return <Archive {...iconProps} className="text-yellow-600" />;
} }
if (['txt', 'md', 'doc', 'docx', 'pdf', 'rtf'].includes(extension)) { if (['txt', 'md', 'doc', 'docx', 'pdf', 'rtf'].includes(extension)) {
return <FileText {...iconProps} className="text-red-600" />; return <FileText {...iconProps} className="text-destructive" />;
} }
return <File {...iconProps} />; return <File {...iconProps} />;
}; };
@ -272,7 +272,7 @@ export const GlobalFileViewer: React.FC<GlobalFileViewerProps> = ({
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => handleRemove(file)} onClick={() => handleRemove(file)}
className="flex items-center gap-1 text-red-600 hover:text-red-700" className="flex items-center gap-1 text-destructive hover:text-red-700"
> >
<Trash2 className="w-3 h-3" /> <Trash2 className="w-3 h-3" />
</Button> </Button>

View File

@ -179,7 +179,7 @@ export default function BatchJobModal({
const getStatusColor = (status: string) => { const getStatusColor = (status: string) => {
switch (status) { switch (status) {
case 'Y': return 'bg-green-100 text-green-800'; case 'Y': return 'bg-green-100 text-green-800';
case 'N': return 'bg-red-100 text-red-800'; case 'N': return 'bg-destructive/20 text-red-800';
default: return 'bg-gray-100 text-gray-800'; default: return 'bg-gray-100 text-gray-800';
} }
}; };
@ -314,29 +314,29 @@ export default function BatchJobModal({
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-4">
<div className="p-4 border rounded-lg"> <div className="p-4 border rounded-lg">
<div className="text-2xl font-bold text-blue-600"> <div className="text-2xl font-bold text-primary">
{formData.execution_count || 0} {formData.execution_count || 0}
</div> </div>
<div className="text-sm text-gray-600"> </div> <div className="text-sm text-muted-foreground"> </div>
</div> </div>
<div className="p-4 border rounded-lg"> <div className="p-4 border rounded-lg">
<div className="text-2xl font-bold text-green-600"> <div className="text-2xl font-bold text-green-600">
{formData.success_count || 0} {formData.success_count || 0}
</div> </div>
<div className="text-sm text-gray-600"> </div> <div className="text-sm text-muted-foreground"> </div>
</div> </div>
<div className="p-4 border rounded-lg"> <div className="p-4 border rounded-lg">
<div className="text-2xl font-bold text-red-600"> <div className="text-2xl font-bold text-destructive">
{formData.failure_count || 0} {formData.failure_count || 0}
</div> </div>
<div className="text-sm text-gray-600"> </div> <div className="text-sm text-muted-foreground"> </div>
</div> </div>
</div> </div>
{formData.last_executed_at && ( {formData.last_executed_at && (
<div className="text-sm text-gray-600"> <div className="text-sm text-muted-foreground">
: {new Date(formData.last_executed_at).toLocaleString()} : {new Date(formData.last_executed_at).toLocaleString()}
</div> </div>
)} )}

View File

@ -55,7 +55,7 @@ export function CategoryItem({ category, isSelected, onSelect, onEdit, onDelete
"cursor-pointer transition-colors", "cursor-pointer transition-colors",
category.is_active === "Y" category.is_active === "Y"
? "bg-green-100 text-green-800 hover:bg-green-200 hover:text-green-900" ? "bg-green-100 text-green-800 hover:bg-green-200 hover:text-green-900"
: "bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-gray-700", : "bg-gray-100 text-muted-foreground hover:bg-gray-200 hover:text-gray-700",
updateCategoryMutation.isPending && "cursor-not-allowed opacity-50", updateCategoryMutation.isPending && "cursor-not-allowed opacity-50",
)} )}
onClick={(e) => { onClick={(e) => {
@ -71,7 +71,7 @@ export function CategoryItem({ category, isSelected, onSelect, onEdit, onDelete
{category.is_active === "Y" ? "활성" : "비활성"} {category.is_active === "Y" ? "활성" : "비활성"}
</Badge> </Badge>
</div> </div>
<p className="mt-1 text-sm text-gray-600">{category.category_code}</p> <p className="mt-1 text-sm text-muted-foreground">{category.category_code}</p>
{category.description && <p className="mt-1 text-sm text-gray-500">{category.description}</p>} {category.description && <p className="mt-1 text-sm text-gray-500">{category.description}</p>}
</div> </div>

View File

@ -180,11 +180,11 @@ export function CodeCategoryFormModal({
{...createForm.register("categoryCode")} {...createForm.register("categoryCode")}
disabled={isLoading} disabled={isLoading}
placeholder="카테고리 코드를 입력하세요" placeholder="카테고리 코드를 입력하세요"
className={createForm.formState.errors.categoryCode ? "border-red-500" : ""} className={createForm.formState.errors.categoryCode ? "border-destructive" : ""}
onBlur={() => handleFieldBlur("categoryCode")} onBlur={() => handleFieldBlur("categoryCode")}
/> />
{createForm.formState.errors.categoryCode && ( {createForm.formState.errors.categoryCode && (
<p className="text-sm text-red-600">{createForm.formState.errors.categoryCode.message}</p> <p className="text-sm text-destructive">{createForm.formState.errors.categoryCode.message}</p>
)} )}
{!createForm.formState.errors.categoryCode && ( {!createForm.formState.errors.categoryCode && (
<ValidationMessage <ValidationMessage
@ -200,7 +200,7 @@ export function CodeCategoryFormModal({
{isEditing && editingCategory && ( {isEditing && editingCategory && (
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="categoryCodeDisplay"> </Label> <Label htmlFor="categoryCodeDisplay"> </Label>
<Input id="categoryCodeDisplay" value={editingCategory.category_code} disabled className="bg-gray-50" /> <Input id="categoryCodeDisplay" value={editingCategory.category_code} disabled className="bg-muted" />
<p className="text-sm text-gray-500"> .</p> <p className="text-sm text-gray-500"> .</p>
</div> </div>
)} )}
@ -216,20 +216,20 @@ export function CodeCategoryFormModal({
className={ className={
isEditing isEditing
? updateForm.formState.errors.categoryName ? updateForm.formState.errors.categoryName
? "border-red-500" ? "border-destructive"
: "" : ""
: createForm.formState.errors.categoryName : createForm.formState.errors.categoryName
? "border-red-500" ? "border-destructive"
: "" : ""
} }
onBlur={() => handleFieldBlur("categoryName")} onBlur={() => handleFieldBlur("categoryName")}
/> />
{isEditing {isEditing
? updateForm.formState.errors.categoryName && ( ? updateForm.formState.errors.categoryName && (
<p className="text-sm text-red-600">{updateForm.formState.errors.categoryName.message}</p> <p className="text-sm text-destructive">{updateForm.formState.errors.categoryName.message}</p>
) )
: createForm.formState.errors.categoryName && ( : createForm.formState.errors.categoryName && (
<p className="text-sm text-red-600">{createForm.formState.errors.categoryName.message}</p> <p className="text-sm text-destructive">{createForm.formState.errors.categoryName.message}</p>
)} )}
{!(isEditing ? updateForm.formState.errors.categoryName : createForm.formState.errors.categoryName) && ( {!(isEditing ? updateForm.formState.errors.categoryName : createForm.formState.errors.categoryName) && (
<ValidationMessage <ValidationMessage
@ -251,20 +251,20 @@ export function CodeCategoryFormModal({
className={ className={
isEditing isEditing
? updateForm.formState.errors.categoryNameEng ? updateForm.formState.errors.categoryNameEng
? "border-red-500" ? "border-destructive"
: "" : ""
: createForm.formState.errors.categoryNameEng : createForm.formState.errors.categoryNameEng
? "border-red-500" ? "border-destructive"
: "" : ""
} }
onBlur={() => handleFieldBlur("categoryNameEng")} onBlur={() => handleFieldBlur("categoryNameEng")}
/> />
{isEditing {isEditing
? updateForm.formState.errors.categoryNameEng && ( ? updateForm.formState.errors.categoryNameEng && (
<p className="text-sm text-red-600">{updateForm.formState.errors.categoryNameEng.message}</p> <p className="text-sm text-destructive">{updateForm.formState.errors.categoryNameEng.message}</p>
) )
: createForm.formState.errors.categoryNameEng && ( : createForm.formState.errors.categoryNameEng && (
<p className="text-sm text-red-600">{createForm.formState.errors.categoryNameEng.message}</p> <p className="text-sm text-destructive">{createForm.formState.errors.categoryNameEng.message}</p>
)} )}
{!(isEditing {!(isEditing
? updateForm.formState.errors.categoryNameEng ? updateForm.formState.errors.categoryNameEng
@ -289,20 +289,20 @@ export function CodeCategoryFormModal({
className={ className={
isEditing isEditing
? updateForm.formState.errors.description ? updateForm.formState.errors.description
? "border-red-500" ? "border-destructive"
: "" : ""
: createForm.formState.errors.description : createForm.formState.errors.description
? "border-red-500" ? "border-destructive"
: "" : ""
} }
onBlur={() => handleFieldBlur("description")} onBlur={() => handleFieldBlur("description")}
/> />
{isEditing {isEditing
? updateForm.formState.errors.description && ( ? updateForm.formState.errors.description && (
<p className="text-sm text-red-600">{updateForm.formState.errors.description.message}</p> <p className="text-sm text-destructive">{updateForm.formState.errors.description.message}</p>
) )
: createForm.formState.errors.description && ( : createForm.formState.errors.description && (
<p className="text-sm text-red-600">{createForm.formState.errors.description.message}</p> <p className="text-sm text-destructive">{createForm.formState.errors.description.message}</p>
)} )}
</div> </div>
@ -320,19 +320,19 @@ export function CodeCategoryFormModal({
className={ className={
isEditing isEditing
? updateForm.formState.errors.sortOrder ? updateForm.formState.errors.sortOrder
? "border-red-500" ? "border-destructive"
: "" : ""
: createForm.formState.errors.sortOrder : createForm.formState.errors.sortOrder
? "border-red-500" ? "border-destructive"
: "" : ""
} }
/> />
{isEditing {isEditing
? updateForm.formState.errors.sortOrder && ( ? updateForm.formState.errors.sortOrder && (
<p className="text-sm text-red-600">{updateForm.formState.errors.sortOrder.message}</p> <p className="text-sm text-destructive">{updateForm.formState.errors.sortOrder.message}</p>
) )
: createForm.formState.errors.sortOrder && ( : createForm.formState.errors.sortOrder && (
<p className="text-sm text-red-600">{createForm.formState.errors.sortOrder.message}</p> <p className="text-sm text-destructive">{createForm.formState.errors.sortOrder.message}</p>
)} )}
</div> </div>

View File

@ -82,7 +82,7 @@ export function CodeCategoryPanel({ selectedCategoryCode, onSelectCategory }: Co
return ( return (
<div className="flex h-full items-center justify-center"> <div className="flex h-full items-center justify-center">
<div className="text-center"> <div className="text-center">
<p className="text-red-600"> .</p> <p className="text-destructive"> .</p>
<Button variant="outline" onClick={() => window.location.reload()} className="mt-2"> <Button variant="outline" onClick={() => window.location.reload()} className="mt-2">
</Button> </Button>
@ -116,7 +116,7 @@ export function CodeCategoryPanel({ selectedCategoryCode, onSelectCategory }: Co
onChange={(e) => setShowActiveOnly(e.target.checked)} onChange={(e) => setShowActiveOnly(e.target.checked)}
className="rounded border-gray-300" className="rounded border-gray-300"
/> />
<label htmlFor="activeOnly" className="text-sm text-gray-600"> <label htmlFor="activeOnly" className="text-sm text-muted-foreground">
</label> </label>
</div> </div>

View File

@ -121,7 +121,7 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
return ( return (
<div className="flex h-full items-center justify-center"> <div className="flex h-full items-center justify-center">
<div className="text-center"> <div className="text-center">
<p className="text-red-600"> .</p> <p className="text-destructive"> .</p>
<Button variant="outline" onClick={() => window.location.reload()} className="mt-2"> <Button variant="outline" onClick={() => window.location.reload()} className="mt-2">
</Button> </Button>
@ -155,7 +155,7 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
onChange={(e) => setShowActiveOnly(e.target.checked)} onChange={(e) => setShowActiveOnly(e.target.checked)}
className="rounded border-gray-300" className="rounded border-gray-300"
/> />
<label htmlFor="activeOnlyCodes" className="text-sm text-gray-600"> <label htmlFor="activeOnlyCodes" className="text-sm text-muted-foreground">
</label> </label>
</div> </div>
@ -221,13 +221,13 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
"transition-colors", "transition-colors",
activeCode.isActive === "Y" || activeCode.is_active === "Y" activeCode.isActive === "Y" || activeCode.is_active === "Y"
? "bg-green-100 text-green-800" ? "bg-green-100 text-green-800"
: "bg-gray-100 text-gray-600", : "bg-gray-100 text-muted-foreground",
)} )}
> >
{activeCode.isActive === "Y" || activeCode.is_active === "Y" ? "활성" : "비활성"} {activeCode.isActive === "Y" || activeCode.is_active === "Y" ? "활성" : "비활성"}
</Badge> </Badge>
</div> </div>
<p className="mt-1 text-sm text-gray-600"> <p className="mt-1 text-sm text-muted-foreground">
{activeCode.codeValue || activeCode.code_value} {activeCode.codeValue || activeCode.code_value}
</p> </p>
{activeCode.description && ( {activeCode.description && (

View File

@ -168,7 +168,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
{...form.register("codeValue")} {...form.register("codeValue")}
disabled={isLoading || isEditing} // 수정 시에는 비활성화 disabled={isLoading || isEditing} // 수정 시에는 비활성화
placeholder="코드값을 입력하세요" placeholder="코드값을 입력하세요"
className={(form.formState.errors as any)?.codeValue ? "border-red-500" : ""} className={(form.formState.errors as any)?.codeValue ? "border-destructive" : ""}
onBlur={(e) => { onBlur={(e) => {
const value = e.target.value.trim(); const value = e.target.value.trim();
if (value && !isEditing) { if (value && !isEditing) {
@ -180,7 +180,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
}} }}
/> />
{(form.formState.errors as any)?.codeValue && ( {(form.formState.errors as any)?.codeValue && (
<p className="text-sm text-red-600">{getErrorMessage((form.formState.errors as any)?.codeValue)}</p> <p className="text-sm text-destructive">{getErrorMessage((form.formState.errors as any)?.codeValue)}</p>
)} )}
{!isEditing && !(form.formState.errors as any)?.codeValue && ( {!isEditing && !(form.formState.errors as any)?.codeValue && (
<ValidationMessage <ValidationMessage
@ -199,7 +199,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
{...form.register("codeName")} {...form.register("codeName")}
disabled={isLoading} disabled={isLoading}
placeholder="코드명을 입력하세요" placeholder="코드명을 입력하세요"
className={form.formState.errors.codeName ? "border-red-500" : ""} className={form.formState.errors.codeName ? "border-destructive" : ""}
onBlur={(e) => { onBlur={(e) => {
const value = e.target.value.trim(); const value = e.target.value.trim();
if (value) { if (value) {
@ -211,7 +211,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
}} }}
/> />
{form.formState.errors.codeName && ( {form.formState.errors.codeName && (
<p className="text-sm text-red-600">{getErrorMessage(form.formState.errors.codeName)}</p> <p className="text-sm text-destructive">{getErrorMessage(form.formState.errors.codeName)}</p>
)} )}
{!form.formState.errors.codeName && ( {!form.formState.errors.codeName && (
<ValidationMessage <ValidationMessage
@ -230,7 +230,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
{...form.register("codeNameEng")} {...form.register("codeNameEng")}
disabled={isLoading} disabled={isLoading}
placeholder="코드 영문명을 입력하세요" placeholder="코드 영문명을 입력하세요"
className={form.formState.errors.codeNameEng ? "border-red-500" : ""} className={form.formState.errors.codeNameEng ? "border-destructive" : ""}
onBlur={(e) => { onBlur={(e) => {
const value = e.target.value.trim(); const value = e.target.value.trim();
if (value) { if (value) {
@ -242,7 +242,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
}} }}
/> />
{form.formState.errors.codeNameEng && ( {form.formState.errors.codeNameEng && (
<p className="text-sm text-red-600">{getErrorMessage(form.formState.errors.codeNameEng)}</p> <p className="text-sm text-destructive">{getErrorMessage(form.formState.errors.codeNameEng)}</p>
)} )}
{!form.formState.errors.codeNameEng && ( {!form.formState.errors.codeNameEng && (
<ValidationMessage <ValidationMessage
@ -262,10 +262,10 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
disabled={isLoading} disabled={isLoading}
placeholder="설명을 입력하세요" placeholder="설명을 입력하세요"
rows={3} rows={3}
className={form.formState.errors.description ? "border-red-500" : ""} className={form.formState.errors.description ? "border-destructive" : ""}
/> />
{form.formState.errors.description && ( {form.formState.errors.description && (
<p className="text-sm text-red-600">{getErrorMessage(form.formState.errors.description)}</p> <p className="text-sm text-destructive">{getErrorMessage(form.formState.errors.description)}</p>
)} )}
</div> </div>
@ -278,10 +278,10 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
{...form.register("sortOrder", { valueAsNumber: true })} {...form.register("sortOrder", { valueAsNumber: true })}
disabled={isLoading} disabled={isLoading}
min={1} min={1}
className={form.formState.errors.sortOrder ? "border-red-500" : ""} className={form.formState.errors.sortOrder ? "border-destructive" : ""}
/> />
{form.formState.errors.sortOrder && ( {form.formState.errors.sortOrder && (
<p className="text-sm text-red-600">{getErrorMessage(form.formState.errors.sortOrder)}</p> <p className="text-sm text-destructive">{getErrorMessage(form.formState.errors.sortOrder)}</p>
)} )}
</div> </div>

View File

@ -188,7 +188,7 @@ export function ColumnDefinitionTable({ columns, onChange, disabled = false }: C
const hasRowError = rowErrors.length > 0; const hasRowError = rowErrors.length > 0;
return ( return (
<TableRow key={index} className={hasRowError ? "bg-red-50" : ""}> <TableRow key={index} className={hasRowError ? "bg-destructive/10" : ""}>
<TableCell> <TableCell>
<div className="space-y-1"> <div className="space-y-1">
<Input <Input
@ -199,7 +199,7 @@ export function ColumnDefinitionTable({ columns, onChange, disabled = false }: C
className={hasRowError ? "border-red-300" : ""} className={hasRowError ? "border-red-300" : ""}
/> />
{rowErrors.length > 0 && ( {rowErrors.length > 0 && (
<div className="space-y-1 text-xs text-red-600"> <div className="space-y-1 text-xs text-destructive">
{rowErrors.map((error, i) => ( {rowErrors.map((error, i) => (
<div key={i}>{error}</div> <div key={i}>{error}</div>
))} ))}

View File

@ -248,7 +248,7 @@ export function CreateTableModal({ isOpen, onClose, onSuccess }: CreateTableModa
placeholder="예: customer_info" placeholder="예: customer_info"
className={tableNameError ? "border-red-300" : ""} className={tableNameError ? "border-red-300" : ""}
/> />
{tableNameError && <p className="text-sm text-red-600">{tableNameError}</p>} {tableNameError && <p className="text-sm text-destructive">{tableNameError}</p>}
<p className="text-muted-foreground text-xs"> , // </p> <p className="text-muted-foreground text-xs"> , // </p>
</div> </div>

View File

@ -271,14 +271,14 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
{log.success ? ( {log.success ? (
<CheckCircle2 className="h-4 w-4 text-green-600" /> <CheckCircle2 className="h-4 w-4 text-green-600" />
) : ( ) : (
<XCircle className="h-4 w-4 text-red-600" /> <XCircle className="h-4 w-4 text-destructive" />
)} )}
<span className={log.success ? "text-green-600" : "text-red-600"}> <span className={log.success ? "text-green-600" : "text-destructive"}>
{log.success ? "성공" : "실패"} {log.success ? "성공" : "실패"}
</span> </span>
</div> </div>
{log.error_message && ( {log.error_message && (
<div className="mt-1 max-w-xs truncate text-xs text-red-600">{log.error_message}</div> <div className="mt-1 max-w-xs truncate text-xs text-destructive">{log.error_message}</div>
)} )}
</TableCell> </TableCell>
@ -325,7 +325,7 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
<CardTitle className="text-sm font-medium"></CardTitle> <CardTitle className="text-sm font-medium"></CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold text-red-600">{statistics.failedExecutions}</div> <div className="text-2xl font-bold text-destructive">{statistics.failedExecutions}</div>
</CardContent> </CardContent>
</Card> </Card>
@ -374,13 +374,13 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
{statistics.recentFailures.length > 0 && ( {statistics.recentFailures.length > 0 && (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="text-base text-red-600"> </CardTitle> <CardTitle className="text-base text-destructive"> </CardTitle>
<CardDescription> DDL .</CardDescription> <CardDescription> DDL .</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-2"> <div className="space-y-2">
{statistics.recentFailures.map((failure, index) => ( {statistics.recentFailures.map((failure, index) => (
<div key={index} className="rounded-lg border border-red-200 bg-red-50 p-3"> <div key={index} className="rounded-lg border border-destructive/20 bg-destructive/10 p-3">
<div className="mb-1 flex items-center justify-between"> <div className="mb-1 flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Badge variant={getDDLTypeBadgeVariant(failure.ddl_type)}>{failure.ddl_type}</Badge> <Badge variant={getDDLTypeBadgeVariant(failure.ddl_type)}>{failure.ddl_type}</Badge>
@ -390,7 +390,7 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
{format(new Date(failure.executed_at), "MM-dd HH:mm", { locale: ko })} {format(new Date(failure.executed_at), "MM-dd HH:mm", { locale: ko })}
</span> </span>
</div> </div>
<div className="text-sm text-red-600">{failure.error_message}</div> <div className="text-sm text-destructive">{failure.error_message}</div>
</div> </div>
))} ))}
</div> </div>

View File

@ -120,7 +120,7 @@ export function DiskUsageSummary({ diskUsageInfo, isLoading, onRefresh }: DiskUs
<div className="mt-2 h-2 w-full rounded-full bg-gray-200"> <div className="mt-2 h-2 w-full rounded-full bg-gray-200">
<div <div
className={`h-2 rounded-full transition-all duration-300 ${ className={`h-2 rounded-full transition-all duration-300 ${
summary.totalSizeMB > 1000 ? "bg-red-500" : summary.totalSizeMB > 500 ? "bg-yellow-500" : "bg-green-500" summary.totalSizeMB > 1000 ? "bg-destructive/100" : summary.totalSizeMB > 500 ? "bg-yellow-500" : "bg-green-500"
}`} }`}
style={{ style={{
width: `${Math.min((summary.totalSizeMB / 2000) * 100, 100)}%`, width: `${Math.min((summary.totalSizeMB / 2000) * 100, 100)}%`,

View File

@ -450,7 +450,7 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
className={`rounded-md border p-3 text-sm ${ className={`rounded-md border p-3 text-sm ${
testResult.success testResult.success
? "border-green-200 bg-green-50 text-green-800" ? "border-green-200 bg-green-50 text-green-800"
: "border-red-200 bg-red-50 text-red-800" : "border-destructive/20 bg-destructive/10 text-red-800"
}`} }`}
> >
<div className="font-medium">{testResult.success ? "✅ 연결 성공" : "❌ 연결 실패"}</div> <div className="font-medium">{testResult.success ? "✅ 연결 성공" : "❌ 연결 실패"}</div>
@ -469,7 +469,7 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
{!testResult.success && testResult.error && ( {!testResult.success && testResult.error && (
<div className="mt-2 text-xs"> <div className="mt-2 text-xs">
<div> : {testResult.error.code}</div> <div> : {testResult.error.code}</div>
{testResult.error.details && <div className="mt-1 text-red-600">{testResult.error.details}</div>} {testResult.error.details && <div className="mt-1 text-destructive">{testResult.error.details}</div>}
</div> </div>
)} )}
</div> </div>

View File

@ -238,10 +238,10 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<div className="mb-6 flex items-center justify-center"> <div className="mb-6 flex items-center justify-center">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div <div
className={`flex items-center gap-2 ${step === "basic" ? "text-blue-600" : step === "template" || step === "advanced" ? "text-green-600" : "text-gray-400"}`} className={`flex items-center gap-2 ${step === "basic" ? "text-primary" : step === "template" || step === "advanced" ? "text-green-600" : "text-gray-400"}`}
> >
<div <div
className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${step === "basic" ? "bg-blue-100 text-blue-600" : step === "template" || step === "advanced" ? "bg-green-100 text-green-600" : "bg-gray-100"}`} className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${step === "basic" ? "bg-primary/20 text-primary" : step === "template" || step === "advanced" ? "bg-green-100 text-green-600" : "bg-gray-100"}`}
> >
1 1
</div> </div>
@ -249,19 +249,19 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
</div> </div>
<div className="h-px w-8 bg-gray-300" /> <div className="h-px w-8 bg-gray-300" />
<div <div
className={`flex items-center gap-2 ${step === "template" ? "text-blue-600" : step === "advanced" ? "text-green-600" : "text-gray-400"}`} className={`flex items-center gap-2 ${step === "template" ? "text-primary" : step === "advanced" ? "text-green-600" : "text-gray-400"}`}
> >
<div <div
className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${step === "template" ? "bg-blue-100 text-blue-600" : step === "advanced" ? "bg-green-100 text-green-600" : "bg-gray-100"}`} className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${step === "template" ? "bg-primary/20 text-primary" : step === "advanced" ? "bg-green-100 text-green-600" : "bg-gray-100"}`}
> >
2 2
</div> </div>
<span className="text-sm font-medium">릿 </span> <span className="text-sm font-medium">릿 </span>
</div> </div>
<div className="h-px w-8 bg-gray-300" /> <div className="h-px w-8 bg-gray-300" />
<div className={`flex items-center gap-2 ${step === "advanced" ? "text-blue-600" : "text-gray-400"}`}> <div className={`flex items-center gap-2 ${step === "advanced" ? "text-primary" : "text-gray-400"}`}>
<div <div
className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${step === "advanced" ? "bg-blue-100 text-blue-600" : "bg-gray-100"}`} className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${step === "advanced" ? "bg-primary/20 text-primary" : "bg-gray-100"}`}
> >
3 3
</div> </div>
@ -304,13 +304,13 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<Card <Card
key={category.id} key={category.id}
className={`cursor-pointer transition-all ${ className={`cursor-pointer transition-all ${
formData.category === category.id ? "bg-blue-50 ring-2 ring-blue-500" : "hover:bg-gray-50" formData.category === category.id ? "bg-accent ring-2 ring-blue-500" : "hover:bg-gray-50"
}`} }`}
onClick={() => setFormData((prev) => ({ ...prev, category: category.id }))} onClick={() => setFormData((prev) => ({ ...prev, category: category.id }))}
> >
<CardContent className="p-4"> <CardContent className="p-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<IconComponent className="h-5 w-5 text-gray-600" /> <IconComponent className="h-5 w-5 text-muted-foreground" />
<div> <div>
<div className="font-medium">{category.name}</div> <div className="font-medium">{category.name}</div>
<div className="text-xs text-gray-500">{category.description}</div> <div className="text-xs text-gray-500">{category.description}</div>
@ -346,7 +346,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<Card <Card
key={template.id} key={template.id}
className={`cursor-pointer transition-all ${ className={`cursor-pointer transition-all ${
formData.template === template.id ? "bg-blue-50 ring-2 ring-blue-500" : "hover:bg-gray-50" formData.template === template.id ? "bg-accent ring-2 ring-blue-500" : "hover:bg-gray-50"
}`} }`}
onClick={() => onClick={() =>
setFormData((prev) => ({ setFormData((prev) => ({
@ -362,7 +362,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<div className="font-medium">{template.name}</div> <div className="font-medium">{template.name}</div>
<Badge variant="secondary">{template.zones} </Badge> <Badge variant="secondary">{template.zones} </Badge>
</div> </div>
<div className="text-sm text-gray-600">{template.description}</div> <div className="text-sm text-muted-foreground">{template.description}</div>
<div className="text-xs text-gray-500">: {template.example}</div> <div className="text-xs text-gray-500">: {template.example}</div>
<div className="rounded bg-gray-100 p-2 text-center font-mono text-xs">{template.icon}</div> <div className="rounded bg-gray-100 p-2 text-center font-mono text-xs">{template.icon}</div>
</div> </div>
@ -427,7 +427,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<div className="space-y-4"> <div className="space-y-4">
{generationResult ? ( {generationResult ? (
<Alert <Alert
className={generationResult.success ? "border-green-200 bg-green-50" : "border-red-200 bg-red-50"} className={generationResult.success ? "border-green-200 bg-green-50" : "border-destructive/20 bg-destructive/10"}
> >
<Info className="h-4 w-4" /> <Info className="h-4 w-4" />
<AlertDescription className={generationResult.success ? "text-green-800" : "text-red-800"}> <AlertDescription className={generationResult.success ? "text-green-800" : "text-red-800"}>
@ -479,7 +479,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<div> <div>
<strong> :</strong> <strong> :</strong>
</div> </div>
<ul className="ml-4 space-y-1 text-xs text-gray-600"> <ul className="ml-4 space-y-1 text-xs text-muted-foreground">
<li> {formData.name.toLowerCase()}/index.ts</li> <li> {formData.name.toLowerCase()}/index.ts</li>
<li> <li>
{formData.name.toLowerCase()}/{formData.name}Layout.tsx {formData.name.toLowerCase()}/{formData.name}Layout.tsx

View File

@ -826,10 +826,10 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
{/* 선택된 화면 정보 표시 */} {/* 선택된 화면 정보 표시 */}
{selectedScreen && ( {selectedScreen && (
<div className="rounded-md border bg-blue-50 p-3"> <div className="rounded-md border bg-accent p-3">
<div className="text-sm font-medium text-blue-900">{selectedScreen.screenName}</div> <div className="text-sm font-medium text-blue-900">{selectedScreen.screenName}</div>
<div className="text-xs text-blue-600">: {selectedScreen.screenCode}</div> <div className="text-xs text-primary">: {selectedScreen.screenCode}</div>
<div className="text-xs text-blue-600"> URL: {formData.menuUrl}</div> <div className="text-xs text-primary"> URL: {formData.menuUrl}</div>
</div> </div>
)} )}
</div> </div>

View File

@ -828,7 +828,7 @@ export const MenuManagement: React.FC = () => {
<CardContent className="space-y-3 pt-4"> <CardContent className="space-y-3 pt-4">
<Card <Card
className={`cursor-pointer transition-all ${ className={`cursor-pointer transition-all ${
selectedMenuType === "admin" ? "border-blue-500 bg-blue-50" : "hover:border-gray-300" selectedMenuType === "admin" ? "border-primary bg-accent" : "hover:border-gray-300"
}`} }`}
onClick={() => handleMenuTypeChange("admin")} onClick={() => handleMenuTypeChange("admin")}
> >
@ -836,7 +836,7 @@ export const MenuManagement: React.FC = () => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h3 className="font-medium">{getUITextSync("menu.management.admin")}</h3> <h3 className="font-medium">{getUITextSync("menu.management.admin")}</h3>
<p className="mt-1 text-sm text-gray-600"> <p className="mt-1 text-sm text-muted-foreground">
{getUITextSync("menu.management.admin.description")} {getUITextSync("menu.management.admin.description")}
</p> </p>
</div> </div>
@ -849,7 +849,7 @@ export const MenuManagement: React.FC = () => {
<Card <Card
className={`cursor-pointer transition-all ${ className={`cursor-pointer transition-all ${
selectedMenuType === "user" ? "border-blue-500 bg-blue-50" : "hover:border-gray-300" selectedMenuType === "user" ? "border-primary bg-accent" : "hover:border-gray-300"
}`} }`}
onClick={() => handleMenuTypeChange("user")} onClick={() => handleMenuTypeChange("user")}
> >
@ -857,7 +857,7 @@ export const MenuManagement: React.FC = () => {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h3 className="font-medium">{getUITextSync("menu.management.user")}</h3> <h3 className="font-medium">{getUITextSync("menu.management.user")}</h3>
<p className="mt-1 text-sm text-gray-600"> <p className="mt-1 text-sm text-muted-foreground">
{getUITextSync("menu.management.user.description")} {getUITextSync("menu.management.user.description")}
</p> </p>
</div> </div>
@ -997,7 +997,7 @@ export const MenuManagement: React.FC = () => {
</div> </div>
<div className="flex items-end"> <div className="flex items-end">
<div className="text-sm text-gray-600"> <div className="text-sm text-muted-foreground">
{getUITextSync("menu.list.search.result", { count: getCurrentMenus().length })} {getUITextSync("menu.list.search.result", { count: getCurrentMenus().length })}
</div> </div>
</div> </div>
@ -1006,7 +1006,7 @@ export const MenuManagement: React.FC = () => {
<div className="flex-1 overflow-hidden"> <div className="flex-1 overflow-hidden">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<div className="text-sm text-gray-600"> <div className="text-sm text-muted-foreground">
{getUITextSync("menu.list.total", { count: getCurrentMenus().length })} {getUITextSync("menu.list.total", { count: getCurrentMenus().length })}
</div> </div>
<div className="flex space-x-2"> <div className="flex space-x-2">

View File

@ -67,7 +67,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
const getLevelBadge = (level: number) => { const getLevelBadge = (level: number) => {
switch (level) { switch (level) {
case 0: case 0:
return "bg-blue-100 text-blue-800"; return "bg-primary/20 text-blue-800";
case 1: case 1:
return "bg-green-100 text-green-800"; return "bg-green-100 text-green-800";
case 2: case 2:
@ -239,7 +239,7 @@ export const MenuTable: React.FC<MenuTableProps> = ({
</div> </div>
</TableCell> </TableCell>
<TableCell>{seq}</TableCell> <TableCell>{seq}</TableCell>
<TableCell className="text-sm text-gray-600"> <TableCell className="text-sm text-muted-foreground">
<div className="flex flex-col"> <div className="flex flex-col">
<span <span
className={`font-medium ${companyName && companyName !== getText(MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED) ? "text-green-600" : "text-gray-500"}`} className={`font-medium ${companyName && companyName !== getText(MENU_MANAGEMENT_KEYS.STATUS_UNSPECIFIED) ? "text-green-600" : "text-gray-500"}`}
@ -253,12 +253,12 @@ export const MenuTable: React.FC<MenuTableProps> = ({
)} )}
</div> </div>
</TableCell> </TableCell>
<TableCell className="text-left text-sm text-gray-600"> <TableCell className="text-left text-sm text-muted-foreground">
<div className="max-w-[200px]"> <div className="max-w-[200px]">
{menuUrl ? ( {menuUrl ? (
<div className="group relative"> <div className="group relative">
<div <div
className={`cursor-pointer transition-colors hover:text-blue-600 ${ className={`cursor-pointer transition-colors hover:text-primary ${
menuUrl.length > 30 ? "truncate" : "" menuUrl.length > 30 ? "truncate" : ""
}`} }`}
onClick={() => { onClick={() => {

View File

@ -74,8 +74,8 @@ export default function MonitoringDashboard() {
const getStatusBadge = (status: string) => { const getStatusBadge = (status: string) => {
const variants = { const variants = {
completed: "bg-green-100 text-green-800", completed: "bg-green-100 text-green-800",
failed: "bg-red-100 text-red-800", failed: "bg-destructive/20 text-red-800",
running: "bg-blue-100 text-blue-800", running: "bg-primary/20 text-blue-800",
pending: "bg-yellow-100 text-yellow-800", pending: "bg-yellow-100 text-yellow-800",
cancelled: "bg-gray-100 text-gray-800", cancelled: "bg-gray-100 text-gray-800",
}; };
@ -129,7 +129,7 @@ export default function MonitoringDashboard() {
variant="outline" variant="outline"
size="sm" size="sm"
onClick={toggleAutoRefresh} onClick={toggleAutoRefresh}
className={autoRefresh ? "bg-blue-50 text-blue-600" : ""} className={autoRefresh ? "bg-accent text-primary" : ""}
> >
{autoRefresh ? <Pause className="h-4 w-4 mr-1" /> : <Play className="h-4 w-4 mr-1" />} {autoRefresh ? <Pause className="h-4 w-4 mr-1" /> : <Play className="h-4 w-4 mr-1" />}
@ -167,7 +167,7 @@ export default function MonitoringDashboard() {
<div className="text-2xl">🔄</div> <div className="text-2xl">🔄</div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold text-blue-600">{monitoring.running_jobs}</div> <div className="text-2xl font-bold text-primary">{monitoring.running_jobs}</div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
</p> </p>
@ -193,7 +193,7 @@ export default function MonitoringDashboard() {
<div className="text-2xl"></div> <div className="text-2xl"></div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold text-red-600">{monitoring.failed_jobs_today}</div> <div className="text-2xl font-bold text-destructive">{monitoring.failed_jobs_today}</div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
</p> </p>
@ -269,7 +269,7 @@ export default function MonitoringDashboard() {
</TableCell> </TableCell>
<TableCell className="max-w-xs"> <TableCell className="max-w-xs">
{execution.error_message ? ( {execution.error_message ? (
<span className="text-red-600 text-sm truncate block"> <span className="text-destructive text-sm truncate block">
{execution.error_message} {execution.error_message}
</span> </span>
) : ( ) : (

View File

@ -673,7 +673,7 @@ export default function MultiLangPage() {
<button <button
onClick={() => setActiveTab("keys")} onClick={() => setActiveTab("keys")}
className={`rounded-t-lg px-3 py-1.5 text-sm font-medium transition-colors ${ className={`rounded-t-lg px-3 py-1.5 text-sm font-medium transition-colors ${
activeTab === "keys" ? "bg-blue-500 text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200" activeTab === "keys" ? "bg-accent0 text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`} }`}
> >
@ -681,7 +681,7 @@ export default function MultiLangPage() {
<button <button
onClick={() => setActiveTab("languages")} onClick={() => setActiveTab("languages")}
className={`rounded-t-lg px-3 py-1.5 text-sm font-medium transition-colors ${ className={`rounded-t-lg px-3 py-1.5 text-sm font-medium transition-colors ${
activeTab === "languages" ? "bg-blue-500 text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200" activeTab === "languages" ? "bg-accent0 text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200"
}`} }`}
> >
@ -698,7 +698,7 @@ export default function MultiLangPage() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<div className="text-sm text-gray-600"> {languages.length} .</div> <div className="text-sm text-muted-foreground"> {languages.length} .</div>
<div className="flex space-x-2"> <div className="flex space-x-2">
{selectedLanguages.size > 0 && ( {selectedLanguages.size > 0 && (
<Button variant="destructive" onClick={handleDeleteLanguages}> <Button variant="destructive" onClick={handleDeleteLanguages}>
@ -759,13 +759,13 @@ export default function MultiLangPage() {
</div> </div>
<div className="flex items-end"> <div className="flex items-end">
<div className="text-sm text-gray-600"> : {getFilteredLangKeys().length}</div> <div className="text-sm text-muted-foreground"> : {getFilteredLangKeys().length}</div>
</div> </div>
</div> </div>
{/* 테이블 영역 */} {/* 테이블 영역 */}
<div> <div>
<div className="mb-2 text-sm text-gray-600">: {getFilteredLangKeys().length}</div> <div className="mb-2 text-sm text-muted-foreground">: {getFilteredLangKeys().length}</div>
<DataTable <DataTable
columns={columns} columns={columns}
data={getFilteredLangKeys()} data={getFilteredLangKeys()}

View File

@ -231,10 +231,10 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
(selectedMenu as any).menu_name_kor || (selectedMenu as any).menu_name_kor ||
"메뉴"} "메뉴"}
</h3> </h3>
<p className="text-sm text-gray-600"> <p className="text-sm text-muted-foreground">
URL: {selectedMenu.menu_url || selectedMenu.MENU_URL || (selectedMenu as any).menu_url || "없음"} URL: {selectedMenu.menu_url || selectedMenu.MENU_URL || (selectedMenu as any).menu_url || "없음"}
</p> </p>
<p className="text-sm text-gray-600"> <p className="text-sm text-muted-foreground">
:{" "} :{" "}
{selectedMenu.menu_desc || selectedMenu.MENU_DESC || (selectedMenu as any).menu_desc || "없음"} {selectedMenu.menu_desc || selectedMenu.MENU_DESC || (selectedMenu as any).menu_desc || "없음"}
</p> </p>
@ -294,7 +294,7 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
{screen.isActive === "Y" ? "활성" : "비활성"} {screen.isActive === "Y" ? "활성" : "비활성"}
</Badge> </Badge>
</div> </div>
<p className="mt-1 text-sm text-gray-600"> <p className="mt-1 text-sm text-muted-foreground">
: {screen.tableName} | : {screen.createdDate.toLocaleDateString()} : {screen.tableName} | : {screen.createdDate.toLocaleDateString()}
</p> </p>
{screen.description && <p className="mt-1 text-sm text-gray-500">{screen.description}</p>} {screen.description && <p className="mt-1 text-sm text-gray-500">{screen.description}</p>}
@ -306,7 +306,7 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
setSelectedScreen(screen); setSelectedScreen(screen);
setShowUnassignDialog(true); setShowUnassignDialog(true);
}} }}
className="text-red-600 hover:text-red-700" className="text-destructive hover:text-red-700"
> >
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</Button> </Button>
@ -347,7 +347,7 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
<div <div
key={screen.screenId} key={screen.screenId}
className={`cursor-pointer rounded-lg border p-3 transition-colors ${ className={`cursor-pointer rounded-lg border p-3 transition-colors ${
selectedScreen?.screenId === screen.screenId ? "border-blue-500 bg-blue-50" : "hover:bg-gray-50" selectedScreen?.screenId === screen.screenId ? "border-primary bg-accent" : "hover:bg-gray-50"
}`} }`}
onClick={() => setSelectedScreen(screen)} onClick={() => setSelectedScreen(screen)}
> >
@ -357,7 +357,7 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
{screen.screenCode} {screen.screenCode}
</Badge> </Badge>
</div> </div>
<p className="mt-1 text-sm text-gray-600">: {screen.tableName}</p> <p className="mt-1 text-sm text-muted-foreground">: {screen.tableName}</p>
</div> </div>
)) ))
)} )}

View File

@ -83,7 +83,7 @@ export function SortableCodeItem({
"cursor-pointer transition-colors", "cursor-pointer transition-colors",
code.isActive === "Y" || code.is_active === "Y" code.isActive === "Y" || code.is_active === "Y"
? "bg-green-100 text-green-800 hover:bg-green-200 hover:text-green-900" ? "bg-green-100 text-green-800 hover:bg-green-200 hover:text-green-900"
: "bg-gray-100 text-gray-600 hover:bg-gray-200 hover:text-gray-700", : "bg-gray-100 text-muted-foreground hover:bg-gray-200 hover:text-gray-700",
updateCodeMutation.isPending && "cursor-not-allowed opacity-50", updateCodeMutation.isPending && "cursor-not-allowed opacity-50",
)} )}
onClick={(e) => { onClick={(e) => {
@ -100,7 +100,7 @@ export function SortableCodeItem({
{code.isActive === "Y" || code.is_active === "Y" ? "활성" : "비활성"} {code.isActive === "Y" || code.is_active === "Y" ? "활성" : "비활성"}
</Badge> </Badge>
</div> </div>
<p className="mt-1 text-sm text-gray-600">{code.codeValue || code.code_value}</p> <p className="mt-1 text-sm text-muted-foreground">{code.codeValue || code.code_value}</p>
{code.description && <p className="mt-1 text-sm text-gray-500">{code.description}</p>} {code.description && <p className="mt-1 text-sm text-gray-500">{code.description}</p>}
</div> </div>

View File

@ -24,9 +24,9 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
case "success": case "success":
return "text-green-600"; return "text-green-600";
case "error": case "error":
return "text-red-600"; return "text-destructive";
default: default:
return "text-blue-600"; return "text-primary";
} }
}; };
@ -37,7 +37,7 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
<DialogTitle className={getTypeColor()}>{title}</DialogTitle> <DialogTitle className={getTypeColor()}>{title}</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="py-4"> <div className="py-4">
<p className="text-sm text-gray-600">{message}</p> <p className="text-sm text-muted-foreground">{message}</p>
</div> </div>
<div className="flex justify-end"> <div className="flex justify-end">
<Button onClick={onClose} className="w-20"> <Button onClick={onClose} className="w-20">
@ -398,7 +398,7 @@ export function UserFormModal({ isOpen, onClose, onSuccess }: UserFormModalProps
{/* 중복확인 결과 메시지 */} {/* 중복확인 결과 메시지 */}
{duplicateCheckMessage && ( {duplicateCheckMessage && (
<div <div
className={`mt-1 text-sm ${duplicateCheckType === "success" ? "text-green-600" : "text-red-600"}`} className={`mt-1 text-sm ${duplicateCheckType === "success" ? "text-green-600" : "text-destructive"}`}
> >
{duplicateCheckMessage} {duplicateCheckMessage}
</div> </div>

View File

@ -196,7 +196,7 @@ export function UserPasswordResetModal({ isOpen, onClose, userId, userName, onSu
</div> </div>
{/* 비밀번호 일치 여부 표시 */} {/* 비밀번호 일치 여부 표시 */}
{showMismatchError && <p className="text-sm text-red-600"> .</p>} {showMismatchError && <p className="text-sm text-destructive"> .</p>}
{isPasswordMatch && <p className="text-sm text-green-600"> .</p>} {isPasswordMatch && <p className="text-sm text-green-600"> .</p>}
</div> </div>

View File

@ -33,8 +33,8 @@ export function UserStatusConfirmDialog({
const currentStatusText = USER_STATUS_LABELS[user.status as keyof typeof USER_STATUS_LABELS] || user.status; const currentStatusText = USER_STATUS_LABELS[user.status as keyof typeof USER_STATUS_LABELS] || user.status;
const newStatusText = USER_STATUS_LABELS[newStatus as keyof typeof USER_STATUS_LABELS] || newStatus; const newStatusText = USER_STATUS_LABELS[newStatus as keyof typeof USER_STATUS_LABELS] || newStatus;
const currentStatusColor = user.status === "active" ? "text-blue-600" : "text-gray-600"; const currentStatusColor = user.status === "active" ? "text-primary" : "text-muted-foreground";
const newStatusColor = newStatus === "active" ? "text-blue-600" : "text-gray-600"; const newStatusColor = newStatus === "active" ? "text-primary" : "text-muted-foreground";
return ( return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onCancel()}> <Dialog open={isOpen} onOpenChange={(open) => !open && onCancel()}>
@ -67,7 +67,7 @@ export function UserStatusConfirmDialog({
<Button variant="outline" onClick={onCancel}> <Button variant="outline" onClick={onCancel}>
</Button> </Button>
<Button onClick={onConfirm} className={newStatus === "active" ? "bg-blue-500 hover:bg-blue-600" : ""}> <Button onClick={onConfirm} variant="default">
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@ -227,7 +227,7 @@ export function CanvasElement({ element, isSelected, onUpdate, onRemove, onSelec
<button <button
className=" className="
w-6 h-6 flex items-center justify-center w-6 h-6 flex items-center justify-center
text-gray-400 hover:bg-blue-500 hover:text-white text-gray-400 hover:bg-accent0 hover:text-white
rounded transition-colors duration-200 rounded transition-colors duration-200
" "
onClick={() => onConfigure(element)} onClick={() => onConfigure(element)}
@ -240,7 +240,7 @@ export function CanvasElement({ element, isSelected, onUpdate, onRemove, onSelec
<button <button
className=" className="
element-close w-6 h-6 flex items-center justify-center element-close w-6 h-6 flex items-center justify-center
text-gray-400 hover:bg-red-500 hover:text-white text-gray-400 hover:bg-destructive/100 hover:text-white
rounded transition-colors duration-200 rounded transition-colors duration-200
" "
onClick={handleRemove} onClick={handleRemove}
@ -259,7 +259,7 @@ export function CanvasElement({ element, isSelected, onUpdate, onRemove, onSelec
{isLoadingData ? ( {isLoadingData ? (
<div className="w-full h-full flex items-center justify-center text-gray-500"> <div className="w-full h-full flex items-center justify-center text-gray-500">
<div className="text-center"> <div className="text-center">
<div className="w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-2" /> <div className="w-6 h-6 border-2 border-primary border-t-transparent rounded-full animate-spin mx-auto mb-2" />
<div className="text-sm"> ...</div> <div className="text-sm"> ...</div>
</div> </div>
</div> </div>

View File

@ -225,7 +225,7 @@ export function ChartConfigPanel({ config, queryResult, onConfigChange }: ChartC
{/* 설정 미리보기 */} {/* 설정 미리보기 */}
<div className="p-3 bg-gray-50 rounded-lg"> <div className="p-3 bg-gray-50 rounded-lg">
<div className="text-sm font-medium text-gray-700 mb-2">📋 </div> <div className="text-sm font-medium text-gray-700 mb-2">📋 </div>
<div className="text-xs text-gray-600 space-y-1"> <div className="text-xs text-muted-foreground space-y-1">
<div><strong>X축:</strong> {currentConfig.xAxis || '미설정'}</div> <div><strong>X축:</strong> {currentConfig.xAxis || '미설정'}</div>
<div> <div>
<strong>Y축:</strong>{' '} <strong>Y축:</strong>{' '}
@ -240,7 +240,7 @@ export function ChartConfigPanel({ config, queryResult, onConfigChange }: ChartC
)} )}
<div><strong> :</strong> {queryResult.rows.length}</div> <div><strong> :</strong> {queryResult.rows.length}</div>
{Array.isArray(currentConfig.yAxis) && currentConfig.yAxis.length > 1 && ( {Array.isArray(currentConfig.yAxis) && currentConfig.yAxis.length > 1 && (
<div className="text-blue-600 mt-2"> <div className="text-primary mt-2">
! !
</div> </div>
)} )}
@ -249,7 +249,7 @@ export function ChartConfigPanel({ config, queryResult, onConfigChange }: ChartC
{/* 필수 필드 확인 */} {/* 필수 필드 확인 */}
{(!currentConfig.xAxis || !currentConfig.yAxis) && ( {(!currentConfig.xAxis || !currentConfig.yAxis) && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg"> <div className="p-3 bg-destructive/10 border border-destructive/20 rounded-lg">
<div className="text-red-800 text-sm"> <div className="text-red-800 text-sm">
X축과 Y축을 . X축과 Y축을 .
</div> </div>

View File

@ -75,7 +75,7 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
w-full min-h-full relative w-full min-h-full relative
bg-gray-100 bg-gray-100
bg-grid-pattern bg-grid-pattern
${isDragOver ? 'bg-blue-50' : ''} ${isDragOver ? 'bg-accent' : ''}
`} `}
style={{ style={{
backgroundImage: ` backgroundImage: `

View File

@ -207,7 +207,7 @@ export default function DashboardDesigner() {
return ( return (
<div className="flex h-full items-center justify-center bg-gray-50"> <div className="flex h-full items-center justify-center bg-gray-50">
<div className="text-center"> <div className="text-center">
<div className="w-12 h-12 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" /> <div className="w-12 h-12 border-4 border-primary border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<div className="text-lg font-medium text-gray-700"> ...</div> <div className="text-lg font-medium text-gray-700"> ...</div>
<div className="text-sm text-gray-500 mt-1"> </div> <div className="text-sm text-gray-500 mt-1"> </div>
</div> </div>
@ -221,7 +221,7 @@ export default function DashboardDesigner() {
<div className="flex-1 relative overflow-auto border-r-2 border-gray-300"> <div className="flex-1 relative overflow-auto border-r-2 border-gray-300">
{/* 편집 중인 대시보드 표시 */} {/* 편집 중인 대시보드 표시 */}
{dashboardTitle && ( {dashboardTitle && (
<div className="absolute top-2 left-2 z-10 bg-blue-500 text-white px-3 py-1 rounded-lg text-sm font-medium shadow-lg"> <div className="absolute top-2 left-2 z-10 bg-accent0 text-white px-3 py-1 rounded-lg text-sm font-medium shadow-lg">
📝 : {dashboardTitle} 📝 : {dashboardTitle}
</div> </div>
)} )}

View File

@ -31,7 +31,7 @@ export function DashboardSidebar() {
type="chart" type="chart"
subtype="bar" subtype="bar"
onDragStart={handleDragStart} onDragStart={handleDragStart}
className="border-l-4 border-blue-500" className="border-l-4 border-primary"
/> />
<DraggableItem <DraggableItem
icon="📚" icon="📚"

View File

@ -70,13 +70,13 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
<h2 className="text-xl font-semibold text-gray-800"> <h2 className="text-xl font-semibold text-gray-800">
{element.title} {element.title}
</h2> </h2>
<p className="text-sm text-gray-600 mt-1"> <p className="text-sm text-muted-foreground mt-1">
</p> </p>
</div> </div>
<button <button
onClick={onClose} onClick={onClose}
className="text-gray-400 hover:text-gray-600 text-2xl" className="text-gray-400 hover:text-muted-foreground text-2xl"
> >
× ×
</button> </button>
@ -89,7 +89,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
className={` className={`
px-6 py-3 text-sm font-medium border-b-2 transition-colors px-6 py-3 text-sm font-medium border-b-2 transition-colors
${activeTab === 'query' ${activeTab === 'query'
? 'border-blue-500 text-blue-600 bg-blue-50' ? 'border-primary text-primary bg-accent'
: 'border-transparent text-gray-500 hover:text-gray-700'} : 'border-transparent text-gray-500 hover:text-gray-700'}
`} `}
> >
@ -100,7 +100,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
className={` className={`
px-6 py-3 text-sm font-medium border-b-2 transition-colors px-6 py-3 text-sm font-medium border-b-2 transition-colors
${activeTab === 'chart' ${activeTab === 'chart'
? 'border-blue-500 text-blue-600 bg-blue-50' ? 'border-primary text-primary bg-accent'
: 'border-transparent text-gray-500 hover:text-gray-700'} : 'border-transparent text-gray-500 hover:text-gray-700'}
`} `}
> >
@ -147,7 +147,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
<div className="flex gap-3"> <div className="flex gap-3">
<button <button
onClick={onClose} onClick={onClose}
className="px-4 py-2 text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50" className="px-4 py-2 text-muted-foreground border border-gray-300 rounded-lg hover:bg-gray-50"
> >
</button> </button>
@ -155,7 +155,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
onClick={handleSave} onClick={handleSave}
disabled={!dataSource.query || (!chartConfig.xAxis || !chartConfig.yAxis)} disabled={!dataSource.query || (!chartConfig.xAxis || !chartConfig.yAxis)}
className=" className="
px-4 py-2 bg-blue-500 text-white rounded-lg px-4 py-2 bg-accent0 text-white rounded-lg
hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed
" "
> >

View File

@ -153,7 +153,7 @@ ORDER BY Q4 DESC;`
onClick={executeQuery} onClick={executeQuery}
disabled={isExecuting || !query.trim()} disabled={isExecuting || !query.trim()}
className=" className="
px-3 py-1 bg-blue-500 text-white rounded text-sm px-3 py-1 bg-accent0 text-white rounded text-sm
hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed
flex items-center gap-1 flex items-center gap-1
" "
@ -172,10 +172,10 @@ ORDER BY Q4 DESC;`
{/* 샘플 쿼리 버튼들 */} {/* 샘플 쿼리 버튼들 */}
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<span className="text-sm text-gray-600"> :</span> <span className="text-sm text-muted-foreground"> :</span>
<button <button
onClick={() => insertSampleQuery('comparison')} onClick={() => insertSampleQuery('comparison')}
className="px-2 py-1 text-xs bg-blue-100 hover:bg-blue-200 rounded font-medium" className="px-2 py-1 text-xs bg-primary/20 hover:bg-blue-200 rounded font-medium"
> >
🔥 🔥
</button> </button>
@ -224,7 +224,7 @@ ORDER BY Q4 DESC;`
{/* 새로고침 간격 설정 */} {/* 새로고침 간격 설정 */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<label className="text-sm text-gray-600"> :</label> <label className="text-sm text-muted-foreground"> :</label>
<select <select
value={dataSource?.refreshInterval || 30000} value={dataSource?.refreshInterval || 30000}
onChange={(e) => onDataSourceChange({ onChange={(e) => onDataSourceChange({
@ -246,7 +246,7 @@ ORDER BY Q4 DESC;`
{/* 오류 메시지 */} {/* 오류 메시지 */}
{error && ( {error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg"> <div className="p-3 bg-destructive/10 border border-destructive/20 rounded-lg">
<div className="text-red-800 text-sm font-medium"> </div> <div className="text-red-800 text-sm font-medium"> </div>
<div className="text-red-700 text-sm mt-1">{error}</div> <div className="text-red-700 text-sm mt-1">{error}</div>
</div> </div>
@ -282,7 +282,7 @@ ORDER BY Q4 DESC;`
{queryResult.rows.slice(0, 10).map((row, idx) => ( {queryResult.rows.slice(0, 10).map((row, idx) => (
<tr key={idx} className="border-b border-gray-100"> <tr key={idx} className="border-b border-gray-100">
{queryResult.columns.map((col, colIdx) => ( {queryResult.columns.map((col, colIdx) => (
<td key={colIdx} className="py-1 px-2 text-gray-600"> <td key={colIdx} className="py-1 px-2 text-muted-foreground">
{String(row[col] ?? '')} {String(row[col] ?? '')}
</td> </td>
))} ))}

View File

@ -115,7 +115,7 @@ export function AuthGuard({
console.log("AuthGuard: 로딩 중 - fallback 표시"); console.log("AuthGuard: 로딩 중 - fallback 표시");
return ( return (
<div> <div>
<div className="mb-4 rounded bg-blue-100 p-4"> <div className="mb-4 rounded bg-primary/20 p-4">
<h3 className="font-bold">AuthGuard ...</h3> <h3 className="font-bold">AuthGuard ...</h3>
<pre className="text-xs">{JSON.stringify(authDebugInfo, null, 2)}</pre> <pre className="text-xs">{JSON.stringify(authDebugInfo, null, 2)}</pre>
</div> </div>
@ -129,10 +129,10 @@ export function AuthGuard({
console.log("AuthGuard: 인증 실패 - fallback 표시"); console.log("AuthGuard: 인증 실패 - fallback 표시");
return ( return (
<div> <div>
<div className="mb-4 rounded bg-red-100 p-4"> <div className="mb-4 rounded bg-destructive/20 p-4">
<h3 className="font-bold"> </h3> <h3 className="font-bold"> </h3>
{redirectCountdown !== null && ( {redirectCountdown !== null && (
<div className="mb-2 text-red-600"> <div className="mb-2 text-destructive">
<strong> :</strong> {redirectCountdown} {redirectTo} <strong> :</strong> {redirectCountdown} {redirectTo}
</div> </div>
)} )}
@ -150,7 +150,7 @@ export function AuthGuard({
<div className="mb-4 rounded bg-orange-100 p-4"> <div className="mb-4 rounded bg-orange-100 p-4">
<h3 className="font-bold"> </h3> <h3 className="font-bold"> </h3>
{redirectCountdown !== null && ( {redirectCountdown !== null && (
<div className="mb-2 text-red-600"> <div className="mb-2 text-destructive">
<strong> :</strong> {redirectCountdown} {redirectTo} <strong> :</strong> {redirectCountdown} {redirectTo}
</div> </div>
)} )}

View File

@ -9,6 +9,6 @@ export function ErrorMessage({ message }: ErrorMessageProps) {
if (!message) return null; if (!message) return null;
return ( return (
<div className="my-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{message}</div> <div className="my-4 rounded-lg border border-destructive/20 bg-destructive/10 px-4 py-3 text-sm text-red-700">{message}</div>
); );
} }

View File

@ -1,4 +1,4 @@
import { Shield } from "lucide-react"; import Image from "next/image";
import { UI_CONFIG } from "@/constants/auth"; import { UI_CONFIG } from "@/constants/auth";
/** /**
@ -7,10 +7,16 @@ import { UI_CONFIG } from "@/constants/auth";
export function LoginHeader() { export function LoginHeader() {
return ( return (
<div className="text-center"> <div className="text-center">
<div className="mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-xl bg-slate-900 shadow-lg"> <div className="mx-auto mb-2 flex items-center justify-center">
<Shield className="h-10 w-10 text-white" /> <Image
src="/images/vexplor.png"
alt={UI_CONFIG.COMPANY_NAME}
width={180}
height={60}
className="object-contain"
priority
/>
</div> </div>
<h1 className="mb-2 text-3xl font-bold text-slate-900">{UI_CONFIG.COMPANY_NAME}</h1>
</div> </div>
); );
} }

View File

@ -263,7 +263,7 @@ export const createStatusColumn = (accessorKey: string, header: string) => ({
? "bg-gray-50 text-gray-700" ? "bg-gray-50 text-gray-700"
: status === "pending" || status === "대기" : status === "pending" || status === "대기"
? "bg-yellow-50 text-yellow-700" ? "bg-yellow-50 text-yellow-700"
: "bg-red-50 text-red-700", : "bg-destructive/10 text-red-700",
)} )}
> >
{status || "-"} {status || "-"}

View File

@ -42,63 +42,36 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
// 화면의 실제 크기 계산 함수 // 화면의 실제 크기 계산 함수
const calculateScreenDimensions = (components: ComponentData[]) => { const calculateScreenDimensions = (components: ComponentData[]) => {
let maxWidth = 800; // 최소 너비 // 모든 컴포넌트의 경계 찾기
let maxHeight = 600; // 최소 높이 let minX = Infinity;
let minY = Infinity;
let maxX = 0;
let maxY = 0;
console.log("🔍 화면 크기 계산 시작:", { componentsCount: components.length }); components.forEach((component) => {
components.forEach((component, index) => {
// position과 size는 BaseComponent에서 별도 속성으로 관리
const x = parseFloat(component.position?.x?.toString() || "0"); const x = parseFloat(component.position?.x?.toString() || "0");
const y = parseFloat(component.position?.y?.toString() || "0"); const y = parseFloat(component.position?.y?.toString() || "0");
const width = parseFloat(component.size?.width?.toString() || "100"); const width = parseFloat(component.size?.width?.toString() || "100");
const height = parseFloat(component.size?.height?.toString() || "40"); const height = parseFloat(component.size?.height?.toString() || "40");
// 컴포넌트의 오른쪽 끝과 아래쪽 끝 계산 minX = Math.min(minX, x);
const rightEdge = x + width; minY = Math.min(minY, y);
const bottomEdge = y + height; maxX = Math.max(maxX, x + width);
maxY = Math.max(maxY, y + height);
console.log(
`📏 컴포넌트 ${index + 1} (${component.id}): x=${x}, y=${y}, w=${width}, h=${height}, rightEdge=${rightEdge}, bottomEdge=${bottomEdge}`,
);
const newMaxWidth = Math.max(maxWidth, rightEdge + 100); // 여백 증가
const newMaxHeight = Math.max(maxHeight, bottomEdge + 100); // 여백 증가
if (newMaxWidth > maxWidth || newMaxHeight > maxHeight) {
console.log(`🔄 크기 업데이트: ${maxWidth}×${maxHeight}${newMaxWidth}×${newMaxHeight}`);
maxWidth = newMaxWidth;
maxHeight = newMaxHeight;
}
}); });
console.log("📊 컴포넌트 기반 계산 결과:", { maxWidth, maxHeight }); // 컨텐츠 실제 크기 + 넉넉한 여백 (양쪽 각 64px)
const contentWidth = maxX - minX;
const contentHeight = maxY - minY;
const padding = 128; // 좌우 또는 상하 합계 여백
// 브라우저 크기 제한 확인 (더욱 관대하게 설정) const finalWidth = Math.max(contentWidth + padding, 400); // 최소 400px
const maxAllowedWidth = window.innerWidth * 0.98; // 95% -> 98% const finalHeight = Math.max(contentHeight + padding, 300); // 최소 300px
const maxAllowedHeight = window.innerHeight * 0.95; // 90% -> 95%
console.log("📐 크기 제한 정보:", { return {
: { maxWidth, maxHeight }, width: Math.min(finalWidth, window.innerWidth * 0.98),
: { maxAllowedWidth, maxAllowedHeight }, height: Math.min(finalHeight, window.innerHeight * 0.95),
: { width: window.innerWidth, height: window.innerHeight },
});
// 컴포넌트 기반 크기를 우선 적용하되, 브라우저 제한을 고려
const finalDimensions = {
width: Math.min(maxWidth, maxAllowedWidth),
height: Math.min(maxHeight, maxAllowedHeight),
}; };
console.log("✅ 최종 화면 크기:", finalDimensions);
console.log("🔧 크기 적용 분석:", {
width적용: maxWidth <= maxAllowedWidth ? "컴포넌트기준" : "브라우저제한",
height적용: maxHeight <= maxAllowedHeight ? "컴포넌트기준" : "브라우저제한",
: { maxWidth, maxHeight },
최종크기: finalDimensions,
});
return finalDimensions;
}; };
// 전역 모달 이벤트 리스너 // 전역 모달 이벤트 리스너
@ -113,10 +86,24 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
}); });
}; };
const handleCloseModal = () => {
console.log("🚪 ScreenModal 닫기 이벤트 수신");
setModalState({
isOpen: false,
screenId: null,
title: "",
size: "md",
});
setScreenData(null);
setFormData({});
};
window.addEventListener("openScreenModal", handleOpenModal as EventListener); window.addEventListener("openScreenModal", handleOpenModal as EventListener);
window.addEventListener("closeSaveModal", handleCloseModal);
return () => { return () => {
window.removeEventListener("openScreenModal", handleOpenModal as EventListener); window.removeEventListener("openScreenModal", handleOpenModal as EventListener);
window.removeEventListener("closeSaveModal", handleCloseModal);
}; };
}, []); }, []);
@ -190,17 +177,17 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
}; };
} }
// 헤더 높이와 패딩을 고려한 전체 높이 계산 (실제 측정값 기반) // 헤더 높이만 고려 (패딩 제거)
const headerHeight = 80; // DialogHeader + 패딩 (더 정확한 값) const headerHeight = 73; // DialogHeader 실제 높이 (border-b px-6 py-4 포함)
const totalHeight = screenDimensions.height + headerHeight; const totalHeight = screenDimensions.height + headerHeight;
return { return {
className: "overflow-hidden p-0", className: "overflow-hidden p-0",
style: { style: {
width: `${Math.min(screenDimensions.width + 48, window.innerWidth * 0.98)}px`, // 브라우저 제한 적용 width: `${Math.min(screenDimensions.width, window.innerWidth * 0.98)}px`, // 화면 크기 그대로
height: `${Math.min(totalHeight, window.innerHeight * 0.95)}px`, // 브라우저 제한 적용 height: `${Math.min(totalHeight, window.innerHeight * 0.95)}px`, // 헤더 + 화면 높이
maxWidth: "98vw", // 안전장치 maxWidth: "98vw",
maxHeight: "95vh", // 안전장치 maxHeight: "95vh",
}, },
}; };
}; };
@ -215,12 +202,12 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
<DialogDescription>{loading ? "화면을 불러오는 중입니다..." : "화면 내용을 표시합니다."}</DialogDescription> <DialogDescription>{loading ? "화면을 불러오는 중입니다..." : "화면 내용을 표시합니다."}</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex-1 p-4"> <div className="flex-1 flex items-center justify-center overflow-hidden">
{loading ? ( {loading ? (
<div className="flex h-full items-center justify-center"> <div className="flex h-full items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"></div> <div className="mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"></div>
<p className="text-gray-600"> ...</p> <p className="text-muted-foreground"> ...</p>
</div> </div>
</div> </div>
) : screenData ? ( ) : screenData ? (
@ -229,6 +216,9 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
style={{ style={{
width: screenDimensions?.width || 800, width: screenDimensions?.width || 800,
height: screenDimensions?.height || 600, height: screenDimensions?.height || 600,
transformOrigin: 'center center',
maxWidth: '100%',
maxHeight: '100%',
}} }}
> >
{screenData.components.map((component) => ( {screenData.components.map((component) => (
@ -258,7 +248,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
</div> </div>
) : ( ) : (
<div className="flex h-full items-center justify-center"> <div className="flex h-full items-center justify-center">
<p className="text-gray-600"> .</p> <p className="text-muted-foreground"> .</p>
</div> </div>
)} )}
</div> </div>

View File

@ -18,6 +18,6 @@ export function ValidationMessage({ message, isValid, isLoading, className }: Va
} }
return ( return (
<p className={cn("text-sm transition-colors", isValid ? "text-green-600" : "text-red-600", className)}>{message}</p> <p className={cn("text-sm transition-colors", isValid ? "text-green-600" : "text-destructive", className)}>{message}</p>
); );
} }

View File

@ -105,10 +105,10 @@ export function DashboardViewer({ elements, dashboardId, refreshInterval }: Dash
return ( return (
<div className="relative w-full h-full bg-gray-100 overflow-auto"> <div className="relative w-full h-full bg-gray-100 overflow-auto">
{/* 새로고침 상태 표시 */} {/* 새로고침 상태 표시 */}
<div className="absolute top-4 right-4 z-10 bg-white rounded-lg shadow-sm px-3 py-2 text-xs text-gray-600"> <div className="absolute top-4 right-4 z-10 bg-white rounded-lg shadow-sm px-3 py-2 text-xs text-muted-foreground">
: {lastRefresh.toLocaleTimeString()} : {lastRefresh.toLocaleTimeString()}
{Array.from(loadingElements).length > 0 && ( {Array.from(loadingElements).length > 0 && (
<span className="ml-2 text-blue-600"> <span className="ml-2 text-primary">
({Array.from(loadingElements).length} ...) ({Array.from(loadingElements).length} ...)
</span> </span>
)} )}
@ -164,7 +164,7 @@ function ViewerElement({ element, data, isLoading, onRefresh }: ViewerElementPro
<button <button
onClick={onRefresh} onClick={onRefresh}
disabled={isLoading} disabled={isLoading}
className="text-gray-400 hover:text-gray-600 disabled:opacity-50" className="text-gray-400 hover:text-muted-foreground disabled:opacity-50"
title="새로고침" title="새로고침"
> >
{isLoading ? ( {isLoading ? (
@ -203,8 +203,8 @@ function ViewerElement({ element, data, isLoading, onRefresh }: ViewerElementPro
{isLoading && ( {isLoading && (
<div className="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center"> <div className="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-2" /> <div className="w-6 h-6 border-2 border-primary border-t-transparent rounded-full animate-spin mx-auto mb-2" />
<div className="text-sm text-gray-600"> ...</div> <div className="text-sm text-muted-foreground"> ...</div>
</div> </div>
</div> </div>
)} )}

View File

@ -244,7 +244,7 @@ export default function DataFlowList({ onDesignDiagram }: DataFlowListProps) {
</div> </div>
</TableCell> </TableCell>
<TableCell> <TableCell>
<div className="flex items-center text-sm text-gray-600"> <div className="flex items-center text-sm text-muted-foreground">
<Calendar className="mr-1 h-3 w-3 text-gray-400" /> <Calendar className="mr-1 h-3 w-3 text-gray-400" />
{new Date(diagram.updatedAt).toLocaleDateString()} {new Date(diagram.updatedAt).toLocaleDateString()}
</div> </div>
@ -269,7 +269,7 @@ export default function DataFlowList({ onDesignDiagram }: DataFlowListProps) {
<Copy className="mr-2 h-4 w-4" /> <Copy className="mr-2 h-4 w-4" />
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => handleDelete(diagram)} className="text-red-600"> <DropdownMenuItem onClick={() => handleDelete(diagram)} className="text-destructive">
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />
</DropdownMenuItem> </DropdownMenuItem>
@ -302,7 +302,7 @@ export default function DataFlowList({ onDesignDiagram }: DataFlowListProps) {
> >
</Button> </Button>
<span className="text-sm text-gray-600"> <span className="text-sm text-muted-foreground">
{currentPage} / {totalPages} {currentPage} / {totalPages}
</span> </span>
<Button <Button
@ -342,11 +342,11 @@ export default function DataFlowList({ onDesignDiagram }: DataFlowListProps) {
<Dialog open={showDeleteModal} onOpenChange={setShowDeleteModal}> <Dialog open={showDeleteModal} onOpenChange={setShowDeleteModal}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle className="text-red-600"> </DialogTitle> <DialogTitle className="text-destructive"> </DialogTitle>
<DialogDescription> <DialogDescription>
&ldquo;{selectedDiagramForAction?.diagramName}&rdquo; ? &ldquo;{selectedDiagramForAction?.diagramName}&rdquo; ?
<br /> <br />
<span className="font-medium text-red-600"> <span className="font-medium text-destructive">
, . , .
</span> </span>
</DialogDescription> </DialogDescription>

View File

@ -54,7 +54,7 @@ export const DataFlowSidebar: React.FC<DataFlowSidebarProps> = ({
<button <button
onClick={onClearAll} onClick={onClearAll}
className="w-full rounded-lg bg-red-500 p-3 font-medium text-white transition-colors hover:bg-red-600" className="w-full rounded-lg bg-destructive/100 p-3 font-medium text-white transition-colors hover:bg-red-600"
> >
🗑 🗑
</button> </button>
@ -72,7 +72,7 @@ export const DataFlowSidebar: React.FC<DataFlowSidebarProps> = ({
{/* 통계 정보 */} {/* 통계 정보 */}
<div className="mt-6 rounded-lg bg-gray-50 p-4"> <div className="mt-6 rounded-lg bg-gray-50 p-4">
<div className="mb-2 text-sm font-semibold text-gray-700"></div> <div className="mb-2 text-sm font-semibold text-gray-700"></div>
<div className="space-y-1 text-sm text-gray-600"> <div className="space-y-1 text-sm text-muted-foreground">
<div className="flex justify-between"> <div className="flex justify-between">
<span> :</span> <span> :</span>
<span className="font-medium">{nodes.length}</span> <span className="font-medium">{nodes.length}</span>

View File

@ -85,11 +85,11 @@ export const EdgeInfoPanel: React.FC<EdgeInfoPanelProps> = ({
{/* 관계 화살표 */} {/* 관계 화살표 */}
<div className="flex justify-center"> <div className="flex justify-center">
<span className="text-l text-gray-600"></span> <span className="text-l text-muted-foreground"></span>
</div> </div>
{/* To 테이블 */} {/* To 테이블 */}
<div className="rounded-lg border-l-4 border-blue-400 bg-blue-50 p-3"> <div className="rounded-lg border-l-4 border-blue-400 bg-accent p-3">
<div className="mb-2 text-xs font-bold tracking-wide text-blue-700 uppercase">TO</div> <div className="mb-2 text-xs font-bold tracking-wide text-blue-700 uppercase">TO</div>
<div className="mb-2 text-base font-bold text-gray-800">{edgeInfo.toTable}</div> <div className="mb-2 text-base font-bold text-gray-800">{edgeInfo.toTable}</div>
<div className="space-y-1"> <div className="space-y-1">
@ -97,7 +97,7 @@ export const EdgeInfoPanel: React.FC<EdgeInfoPanelProps> = ({
{edgeInfo.toColumns.map((column, index) => ( {edgeInfo.toColumns.map((column, index) => (
<span <span
key={index} key={index}
className="inline-flex items-center rounded-md bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 ring-1 ring-blue-200" className="inline-flex items-center rounded-md bg-primary/20 px-2.5 py-0.5 text-xs font-medium text-blue-800 ring-1 ring-blue-200"
> >
{column} {column}
</span> </span>

View File

@ -134,18 +134,18 @@ export const RelationshipListModal: React.FC<RelationshipListModalProps> = ({
}; };
return ( return (
<div className="pointer-events-auto absolute top-4 right-4 z-40 w-80 rounded-xl border border-blue-200 bg-white shadow-lg"> <div className="pointer-events-auto absolute top-4 right-4 z-40 w-80 rounded-xl border border-primary/20 bg-white shadow-lg">
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between rounded-t-xl border-b border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-3"> <div className="flex items-center justify-between rounded-t-xl border-b border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="rounded-full bg-blue-100 p-1"> <div className="rounded-full bg-primary/20 p-1">
<span className="text-sm text-blue-600">🔗</span> <span className="text-sm text-primary">🔗</span>
</div> </div>
<div className="text-sm font-semibold text-gray-800"> </div> <div className="text-sm font-semibold text-gray-800"> </div>
</div> </div>
<button <button
onClick={onClose} onClick={onClose}
className="flex h-6 w-6 items-center justify-center rounded-full text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600" className="flex h-6 w-6 items-center justify-center rounded-full text-gray-400 transition-colors hover:bg-gray-100 hover:text-muted-foreground"
> >
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@ -159,7 +159,7 @@ export const RelationshipListModal: React.FC<RelationshipListModalProps> = ({
{relationships.map((relationship) => ( {relationships.map((relationship) => (
<div <div
key={relationship.id} key={relationship.id}
className="rounded-lg border border-gray-200 p-3 transition-all hover:border-blue-300 hover:bg-blue-50" className="rounded-lg border border-gray-200 p-3 transition-all hover:border-blue-300 hover:bg-accent"
> >
<div className="mb-1 flex items-center justify-between"> <div className="mb-1 flex items-center justify-between">
<h4 className="text-sm font-medium text-gray-900"> <h4 className="text-sm font-medium text-gray-900">
@ -172,7 +172,7 @@ export const RelationshipListModal: React.FC<RelationshipListModalProps> = ({
e.stopPropagation(); e.stopPropagation();
handleEdit(relationship); handleEdit(relationship);
}} }}
className="flex h-6 w-6 items-center justify-center rounded text-gray-400 hover:bg-blue-100 hover:text-blue-600" className="flex h-6 w-6 items-center justify-center rounded text-gray-400 hover:bg-primary/20 hover:text-primary"
title="관계 편집" title="관계 편집"
> >
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -190,7 +190,7 @@ export const RelationshipListModal: React.FC<RelationshipListModalProps> = ({
e.stopPropagation(); e.stopPropagation();
handleDelete(relationship); handleDelete(relationship);
}} }}
className="flex h-6 w-6 items-center justify-center rounded text-gray-400 hover:bg-red-100 hover:text-red-600" className="flex h-6 w-6 items-center justify-center rounded text-gray-400 hover:bg-destructive/20 hover:text-destructive"
title="관계 삭제" title="관계 삭제"
> >
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@ -204,7 +204,7 @@ export const RelationshipListModal: React.FC<RelationshipListModalProps> = ({
</button> </button>
</div> </div>
</div> </div>
<div className="space-y-1 text-xs text-gray-600"> <div className="space-y-1 text-xs text-muted-foreground">
<p>: {relationship.connectionType}</p> <p>: {relationship.connectionType}</p>
<p>From: {relationship.fromTable}</p> <p>From: {relationship.fromTable}</p>
<p>To: {relationship.toTable}</p> <p>To: {relationship.toTable}</p>

View File

@ -149,26 +149,26 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
onKeyPress={handleKeyPress} onKeyPress={handleKeyPress}
placeholder="예: 사용자-부서 관계도" placeholder="예: 사용자-부서 관계도"
disabled={isLoading} disabled={isLoading}
className={nameError ? "border-red-500 focus:border-red-500" : ""} className={nameError ? "border-destructive focus:border-destructive" : ""}
/> />
{nameError && <p className="text-sm text-red-600">{nameError}</p>} {nameError && <p className="text-sm text-destructive">{nameError}</p>}
</div> </div>
{/* 관계 요약 정보 */} {/* 관계 요약 정보 */}
<div className="grid grid-cols-3 gap-4 rounded-lg bg-gray-50 p-4"> <div className="grid grid-cols-3 gap-4 rounded-lg bg-gray-50 p-4">
<div className="text-center"> <div className="text-center">
<div className="text-2xl font-bold text-blue-600">{relationships.length}</div> <div className="text-2xl font-bold text-primary">{relationships.length}</div>
<div className="text-sm text-gray-600"> </div> <div className="text-sm text-muted-foreground"> </div>
</div> </div>
<div className="text-center"> <div className="text-center">
<div className="text-2xl font-bold text-green-600">{connectedTables.length}</div> <div className="text-2xl font-bold text-green-600">{connectedTables.length}</div>
<div className="text-sm text-gray-600"> </div> <div className="text-sm text-muted-foreground"> </div>
</div> </div>
<div className="text-center"> <div className="text-center">
<div className="text-2xl font-bold text-purple-600"> <div className="text-2xl font-bold text-purple-600">
{relationships.reduce((sum, rel) => sum + rel.fromColumns.length, 0)} {relationships.reduce((sum, rel) => sum + rel.fromColumns.length, 0)}
</div> </div>
<div className="text-sm text-gray-600"> </div> <div className="text-sm text-muted-foreground"> </div>
</div> </div>
</div> </div>
@ -212,7 +212,7 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
{relationship.relationshipName || `${relationship.fromTable}${relationship.toTable}`} {relationship.relationshipName || `${relationship.fromTable}${relationship.toTable}`}
</span> </span>
</div> </div>
<div className="mt-1 text-xs text-gray-600"> <div className="mt-1 text-xs text-muted-foreground">
{relationship.fromTable} {relationship.toTable} {relationship.fromTable} {relationship.toTable}
</div> </div>
</div> </div>

View File

@ -24,12 +24,12 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
canCreateConnection, canCreateConnection,
}) => { }) => {
return ( return (
<div className="pointer-events-auto absolute top-4 left-4 z-40 w-80 rounded-xl border border-blue-200 bg-white shadow-lg"> <div className="pointer-events-auto absolute top-4 left-4 z-40 w-80 rounded-xl border border-primary/20 bg-white shadow-lg">
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between rounded-t-xl border-b border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-3"> <div className="flex items-center justify-between rounded-t-xl border-b border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100"> <div className="flex h-6 w-6 items-center justify-center rounded-full bg-primary/20">
<span className="text-sm text-blue-600">📋</span> <span className="text-sm text-primary">📋</span>
</div> </div>
<div> <div>
<div className="text-sm font-semibold text-gray-800"> </div> <div className="text-sm font-semibold text-gray-800"> </div>
@ -44,7 +44,7 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
</div> </div>
<button <button
onClick={onClose} onClick={onClose}
className="flex h-5 w-5 items-center justify-center rounded-full text-gray-400 hover:bg-gray-100 hover:text-gray-600" className="flex h-5 w-5 items-center justify-center rounded-full text-gray-400 hover:bg-gray-100 hover:text-muted-foreground"
> >
</button> </button>
@ -66,8 +66,8 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
index === 0 index === 0
? "border-l-4 border-emerald-400 bg-emerald-50" ? "border-l-4 border-emerald-400 bg-emerald-50"
: index === 1 : index === 1
? "border-l-4 border-blue-400 bg-blue-50" ? "border-l-4 border-blue-400 bg-accent"
: "bg-gray-50" : "bg-muted"
}`} }`}
> >
<div className="mb-1 flex items-center justify-between"> <div className="mb-1 flex items-center justify-between">
@ -88,7 +88,7 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
</div> </div>
)} )}
</div> </div>
<div className="text-xs text-gray-600">{tableName}</div> <div className="text-xs text-muted-foreground">{tableName}</div>
</div> </div>
{/* 연결 화살표 (마지막이 아닌 경우) */} {/* 연결 화살표 (마지막이 아닌 경우) */}
@ -110,7 +110,7 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
disabled={!canCreateConnection} disabled={!canCreateConnection}
className={`flex flex-1 items-center justify-center gap-1 rounded-lg px-3 py-2 text-xs font-medium transition-colors ${ className={`flex flex-1 items-center justify-center gap-1 rounded-lg px-3 py-2 text-xs font-medium transition-colors ${
canCreateConnection canCreateConnection
? "bg-blue-500 text-white hover:bg-blue-600" ? "bg-accent0 text-white hover:bg-blue-600"
: "cursor-not-allowed bg-gray-300 text-gray-500" : "cursor-not-allowed bg-gray-300 text-gray-500"
}`} }`}
> >
@ -119,7 +119,7 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
</button> </button>
<button <button
onClick={onClear} onClick={onClear}
className="flex flex-1 items-center justify-center gap-1 rounded-lg bg-gray-200 px-3 py-2 text-xs font-medium text-gray-600 hover:bg-gray-300" className="flex flex-1 items-center justify-center gap-1 rounded-lg bg-gray-200 px-3 py-2 text-xs font-medium text-muted-foreground hover:bg-gray-300"
> >
<span>🗑</span> <span>🗑</span>
<span></span> <span></span>

View File

@ -56,7 +56,7 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
<div <div
key={columnKey} key={columnKey}
className={`relative cursor-pointer rounded px-2 py-1 text-xs transition-colors ${ className={`relative cursor-pointer rounded px-2 py-1 text-xs transition-colors ${
isSelected ? "bg-blue-100 text-blue-800 ring-2 ring-blue-500" : "text-gray-700 hover:bg-gray-100" isSelected ? "bg-primary/20 text-blue-800 ring-2 ring-blue-500" : "text-gray-700 hover:bg-gray-100"
}`} }`}
onClick={() => onColumnClick(table.tableName, columnKey)} onClick={() => onColumnClick(table.tableName, columnKey)}
> >

View File

@ -93,7 +93,7 @@ export const TableSelector: React.FC<TableSelectorProps> = ({ companyCode, onTab
</div> </div>
{/* 오류 메시지 */} {/* 오류 메시지 */}
{error && <div className="rounded-lg bg-red-50 p-4 text-sm text-red-600">{error}</div>} {error && <div className="rounded-lg bg-destructive/10 p-4 text-sm text-destructive">{error}</div>}
{/* 테이블 목록 */} {/* 테이블 목록 */}
<div className="max-h-96 space-y-2 overflow-y-auto"> <div className="max-h-96 space-y-2 overflow-y-auto">
@ -114,7 +114,7 @@ export const TableSelector: React.FC<TableSelectorProps> = ({ companyCode, onTab
<Card <Card
key={table.tableName} key={table.tableName}
className={`cursor-pointer transition-all hover:shadow-md ${ className={`cursor-pointer transition-all hover:shadow-md ${
isSelected ? "cursor-not-allowed border-blue-500 bg-blue-50 opacity-60" : "hover:border-gray-300" isSelected ? "cursor-not-allowed border-primary bg-accent opacity-60" : "hover:border-gray-300"
}`} }`}
onDoubleClick={() => !isSelected && handleAddTable(table)} onDoubleClick={() => !isSelected && handleAddTable(table)}
> >
@ -126,10 +126,10 @@ export const TableSelector: React.FC<TableSelectorProps> = ({ companyCode, onTab
</CardHeader> </CardHeader>
<CardContent className="pt-0"> <CardContent className="pt-0">
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center gap-2 text-xs text-gray-600"> <div className="flex items-center gap-2 text-xs text-muted-foreground">
<Database className="h-3 w-3" /> <Database className="h-3 w-3" />
<span className="font-mono">{table.tableName}</span> <span className="font-mono">{table.tableName}</span>
{isSelected && <span className="font-medium text-blue-600">()</span>} {isSelected && <span className="font-medium text-primary">()</span>}
</div> </div>
{table.description && <p className="line-clamp-2 text-xs text-gray-500">{table.description}</p>} {table.description && <p className="line-clamp-2 text-xs text-gray-500">{table.description}</p>}
@ -142,7 +142,7 @@ export const TableSelector: React.FC<TableSelectorProps> = ({ companyCode, onTab
</div> </div>
{/* 통계 정보 */} {/* 통계 정보 */}
<div className="rounded-lg bg-gray-50 p-3 text-xs text-gray-600"> <div className="rounded-lg bg-gray-50 p-3 text-xs text-muted-foreground">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span> : {tables.length}</span> <span> : {tables.length}</span>
{searchTerm && <span> : {filteredTables.length}</span>} {searchTerm && <span> : {filteredTables.length}</span>}

View File

@ -81,7 +81,7 @@ export const ConditionRenderer: React.FC<ConditionRendererProps> = ({
value={condition.logicalOperator || "AND"} value={condition.logicalOperator || "AND"}
onValueChange={(value: "AND" | "OR") => onUpdateCondition(index, "logicalOperator", value)} onValueChange={(value: "AND" | "OR") => onUpdateCondition(index, "logicalOperator", value)}
> >
<SelectTrigger className="h-8 w-24 border-blue-200 bg-blue-50 text-xs"> <SelectTrigger className="h-8 w-24 border-primary/20 bg-accent text-xs">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -92,11 +92,11 @@ export const ConditionRenderer: React.FC<ConditionRendererProps> = ({
)} )}
{/* 그룹 레벨에 따른 들여쓰기 */} {/* 그룹 레벨에 따른 들여쓰기 */}
<div <div
className="flex items-center gap-2 rounded border-2 border-dashed border-blue-300 bg-blue-50/50 p-2" className="flex items-center gap-2 rounded border-2 border-dashed border-blue-300 bg-accent/50 p-2"
style={{ marginLeft: `${(condition.groupLevel || 0) * 20}px` }} style={{ marginLeft: `${(condition.groupLevel || 0) * 20}px` }}
> >
<span className="font-mono text-sm text-blue-600">(</span> <span className="font-mono text-sm text-primary">(</span>
<span className="text-xs text-blue-600"> </span> <span className="text-xs text-primary"> </span>
<Button size="sm" variant="ghost" onClick={() => onRemoveCondition(index)} className="h-6 w-6 p-0"> <Button size="sm" variant="ghost" onClick={() => onRemoveCondition(index)} className="h-6 w-6 p-0">
<Trash2 className="h-3 w-3" /> <Trash2 className="h-3 w-3" />
</Button> </Button>
@ -110,11 +110,11 @@ export const ConditionRenderer: React.FC<ConditionRendererProps> = ({
return ( return (
<div key={condition.id} className="flex items-center gap-2"> <div key={condition.id} className="flex items-center gap-2">
<div <div
className="flex items-center gap-2 rounded border-2 border-dashed border-blue-300 bg-blue-50/50 p-2" className="flex items-center gap-2 rounded border-2 border-dashed border-blue-300 bg-accent/50 p-2"
style={{ marginLeft: `${(condition.groupLevel || 0) * 20}px` }} style={{ marginLeft: `${(condition.groupLevel || 0) * 20}px` }}
> >
<span className="font-mono text-sm text-blue-600">)</span> <span className="font-mono text-sm text-primary">)</span>
<span className="text-xs text-blue-600"> </span> <span className="text-xs text-primary"> </span>
<Button size="sm" variant="ghost" onClick={() => onRemoveCondition(index)} className="h-6 w-6 p-0"> <Button size="sm" variant="ghost" onClick={() => onRemoveCondition(index)} className="h-6 w-6 p-0">
<Trash2 className="h-3 w-3" /> <Trash2 className="h-3 w-3" />
</Button> </Button>
@ -126,7 +126,7 @@ export const ConditionRenderer: React.FC<ConditionRendererProps> = ({
value={conditions[index + 1]?.logicalOperator || "AND"} value={conditions[index + 1]?.logicalOperator || "AND"}
onValueChange={(value: "AND" | "OR") => onUpdateCondition(index + 1, "logicalOperator", value)} onValueChange={(value: "AND" | "OR") => onUpdateCondition(index + 1, "logicalOperator", value)}
> >
<SelectTrigger className="h-8 w-24 border-blue-200 bg-blue-50 text-xs"> <SelectTrigger className="h-8 w-24 border-primary/20 bg-accent text-xs">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -150,7 +150,7 @@ export const ConditionRenderer: React.FC<ConditionRendererProps> = ({
value={condition.logicalOperator || "AND"} value={condition.logicalOperator || "AND"}
onValueChange={(value: "AND" | "OR") => onUpdateCondition(index, "logicalOperator", value)} onValueChange={(value: "AND" | "OR") => onUpdateCondition(index, "logicalOperator", value)}
> >
<SelectTrigger className="h-8 w-24 border-blue-200 bg-blue-50 text-xs"> <SelectTrigger className="h-8 w-24 border-primary/20 bg-accent text-xs">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>

View File

@ -400,7 +400,7 @@ export const WebTypeInput: React.FC<WebTypeInputProps> = ({
multiple={detailSettings.multiple as boolean} multiple={detailSettings.multiple as boolean}
/> />
{value && ( {value && (
<div className="flex items-center gap-2 text-sm text-gray-600"> <div className="flex items-center gap-2 text-sm text-muted-foreground">
<Upload className="h-4 w-4" /> <Upload className="h-4 w-4" />
<span> : {value}</span> <span> : {value}</span>
</div> </div>

View File

@ -84,24 +84,24 @@ export const ActionConditionsSection: React.FC<ActionConditionsSectionProps> = (
<summary <summary
className={`flex cursor-pointer items-center justify-between rounded border p-2 text-xs font-medium hover:bg-gray-50 hover:text-gray-900 ${ className={`flex cursor-pointer items-center justify-between rounded border p-2 text-xs font-medium hover:bg-gray-50 hover:text-gray-900 ${
isConditionRequired && !hasValidConditions isConditionRequired && !hasValidConditions
? "border-red-300 bg-red-50 text-red-700" ? "border-red-300 bg-destructive/10 text-red-700"
: "border-gray-200 text-gray-700" : "border-gray-200 text-gray-700"
}`} }`}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
🔍 🔍
{isConditionRequired ? ( {isConditionRequired ? (
<span className="rounded bg-red-100 px-1 py-0.5 text-xs font-semibold text-red-700"></span> <span className="rounded bg-destructive/20 px-1 py-0.5 text-xs font-semibold text-red-700"></span>
) : ( ) : (
<span className="text-gray-500">()</span> <span className="text-gray-500">()</span>
)} )}
{action.conditions && action.conditions.length > 0 && ( {action.conditions && action.conditions.length > 0 && (
<span className="rounded-full bg-blue-100 px-2 py-0.5 text-xs text-blue-700"> <span className="rounded-full bg-primary/20 px-2 py-0.5 text-xs text-blue-700">
{action.conditions.length} {action.conditions.length}
</span> </span>
)} )}
{isConditionRequired && !hasValidConditions && ( {isConditionRequired && !hasValidConditions && (
<span className="rounded bg-red-100 px-1 py-0.5 text-xs text-red-600"> </span> <span className="rounded bg-destructive/20 px-1 py-0.5 text-xs text-destructive"> </span>
)} )}
</div> </div>
{action.conditions && action.conditions.length > 0 && ( {action.conditions && action.conditions.length > 0 && (
@ -151,8 +151,8 @@ export const ActionConditionsSection: React.FC<ActionConditionsSectionProps> = (
<div <div
className={`rounded border p-3 text-xs ${ className={`rounded border p-3 text-xs ${
isConditionRequired isConditionRequired
? "border-red-200 bg-red-50 text-red-700" ? "border-destructive/20 bg-destructive/10 text-red-700"
: "border-gray-200 bg-gray-50 text-gray-600" : "border-gray-200 bg-gray-50 text-muted-foreground"
}`} }`}
> >
{isConditionRequired ? ( {isConditionRequired ? (

View File

@ -228,7 +228,7 @@ export const ActionFieldMappings: React.FC<ActionFieldMappingsProps> = ({
<div className="mb-2 flex items-center justify-between"> <div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Label className="text-xs font-medium"> </Label> <Label className="text-xs font-medium"> </Label>
<span className="text-xs text-red-600">()</span> <span className="text-xs text-destructive">()</span>
</div> </div>
<Button size="sm" variant="outline" onClick={addFieldMapping} className="h-6 text-xs"> <Button size="sm" variant="outline" onClick={addFieldMapping} className="h-6 text-xs">
<Plus className="mr-1 h-2 w-2" /> <Plus className="mr-1 h-2 w-2" />
@ -244,7 +244,7 @@ export const ActionFieldMappings: React.FC<ActionFieldMappingsProps> = ({
{/* 컴팩트한 매핑 표시 */} {/* 컴팩트한 매핑 표시 */}
<div className="flex items-center gap-2 text-xs"> <div className="flex items-center gap-2 text-xs">
{/* 소스 */} {/* 소스 */}
<div className="flex items-center gap-1 rounded bg-blue-50 px-2 py-1"> <div className="flex items-center gap-1 rounded bg-accent px-2 py-1">
<Select <Select
value={mapping.sourceTable || "__EMPTY__"} value={mapping.sourceTable || "__EMPTY__"}
onValueChange={(value) => { onValueChange={(value) => {
@ -277,7 +277,7 @@ export const ActionFieldMappings: React.FC<ActionFieldMappingsProps> = ({
updateFieldMapping(mappingIndex, "sourceTable", ""); updateFieldMapping(mappingIndex, "sourceTable", "");
updateFieldMapping(mappingIndex, "sourceField", ""); updateFieldMapping(mappingIndex, "sourceField", "");
}} }}
className="ml-1 flex h-4 w-4 items-center justify-center rounded-full text-gray-400 hover:bg-gray-200 hover:text-gray-600" className="ml-1 flex h-4 w-4 items-center justify-center rounded-full text-gray-400 hover:bg-gray-200 hover:text-muted-foreground"
title="소스 테이블 지우기" title="소스 테이블 지우기"
> >
× ×
@ -390,7 +390,7 @@ export const ActionFieldMappings: React.FC<ActionFieldMappingsProps> = ({
{/* 필드 매핑이 없을 때 안내 메시지 */} {/* 필드 매핑이 없을 때 안내 메시지 */}
{action.fieldMappings.length === 0 && ( {action.fieldMappings.length === 0 && (
<div className="rounded border border-red-200 bg-red-50 p-3 text-xs text-red-700"> <div className="rounded border border-destructive/20 bg-destructive/10 p-3 text-xs text-red-700">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<span className="text-red-500"></span> <span className="text-red-500"></span>
<div> <div>

View File

@ -190,7 +190,7 @@ export const ColumnTableSection: React.FC<ColumnTableSectionProps> = ({
: isMapped : isMapped
? "bg-gray-100 text-gray-700" ? "bg-gray-100 text-gray-700"
: oppositeSelectedColumn && !isTypeCompatible : oppositeSelectedColumn && !isTypeCompatible
? "cursor-not-allowed bg-red-50 text-red-400 opacity-60" ? "cursor-not-allowed bg-destructive/10 text-red-400 opacity-60"
: isClickable : isClickable
? "cursor-pointer hover:bg-gray-50" ? "cursor-pointer hover:bg-gray-50"
: "cursor-not-allowed bg-gray-100 text-gray-400" : "cursor-not-allowed bg-gray-100 text-gray-400"
@ -250,7 +250,7 @@ export const ColumnTableSection: React.FC<ColumnTableSectionProps> = ({
: hasDefaultValue : hasDefaultValue
? "bg-gray-100" ? "bg-gray-100"
: oppositeSelectedColumn && !isTypeCompatible : oppositeSelectedColumn && !isTypeCompatible
? "bg-red-50 opacity-60" ? "bg-destructive/10 opacity-60"
: "bg-white" : "bg-white"
}`} }`}
> >
@ -292,7 +292,7 @@ export const ColumnTableSection: React.FC<ColumnTableSectionProps> = ({
{isMapped && ( {isMapped && (
<div className="mt-2 flex items-center gap-2"> <div className="mt-2 flex items-center gap-2">
<span className="truncate text-xs text-blue-600"> {mapping.fromColumnName}</span> <span className="truncate text-xs text-primary"> {mapping.fromColumnName}</span>
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@ -327,7 +327,7 @@ export const ColumnTableSection: React.FC<ColumnTableSectionProps> = ({
</div> </div>
{/* 하단 통계 */} {/* 하단 통계 */}
<div className="rounded-b-lg border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600"> <div className="rounded-b-lg border border-gray-200 bg-gray-50 p-3 text-xs text-muted-foreground">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span> <span>
{isFromTable ? "매핑됨" : "설정됨"}: {mappedCount}/{columns.length} {isFromTable ? "매핑됨" : "설정됨"}: {mappedCount}/{columns.length}

View File

@ -18,14 +18,14 @@ export const ConnectionTypeSelector: React.FC<ConnectionTypeSelectorProps> = ({
<div <div
className={`cursor-pointer rounded-lg border-2 p-3 text-center transition-colors ${ className={`cursor-pointer rounded-lg border-2 p-3 text-center transition-colors ${
config.connectionType === "simple-key" config.connectionType === "simple-key"
? "border-blue-500 bg-blue-50" ? "border-primary bg-accent"
: "border-gray-200 hover:border-gray-300" : "border-gray-200 hover:border-gray-300"
}`} }`}
onClick={() => onConfigChange({ ...config, connectionType: "simple-key" })} onClick={() => onConfigChange({ ...config, connectionType: "simple-key" })}
> >
<Key className="mx-auto h-6 w-6 text-blue-500" /> <Key className="mx-auto h-6 w-6 text-blue-500" />
<div className="mt-1 text-xs font-medium"> </div> <div className="mt-1 text-xs font-medium"> </div>
<div className="text-xs text-gray-600"> </div> <div className="text-xs text-muted-foreground"> </div>
</div> </div>
<div <div
@ -38,7 +38,7 @@ export const ConnectionTypeSelector: React.FC<ConnectionTypeSelectorProps> = ({
> >
<Save className="mx-auto h-6 w-6 text-green-500" /> <Save className="mx-auto h-6 w-6 text-green-500" />
<div className="mt-1 text-xs font-medium"> </div> <div className="mt-1 text-xs font-medium"> </div>
<div className="text-xs text-gray-600"> </div> <div className="text-xs text-muted-foreground"> </div>
</div> </div>
<div <div
@ -54,7 +54,7 @@ export const ConnectionTypeSelector: React.FC<ConnectionTypeSelectorProps> = ({
> >
<Globe className="mx-auto h-6 w-6 text-orange-500" /> <Globe className="mx-auto h-6 w-6 text-orange-500" />
<div className="mt-1 text-xs font-medium"> </div> <div className="mt-1 text-xs font-medium"> </div>
<div className="text-xs text-gray-600">API/ </div> <div className="text-xs text-muted-foreground">API/ </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -299,7 +299,7 @@ export const DeleteConditionPanel: React.FC<DeleteConditionPanelProps> = ({
<SelectContent> <SelectContent>
<SelectItem value="=">=</SelectItem> <SelectItem value="=">=</SelectItem>
<SelectItem value="!="> <SelectItem value="!=">
<span className="text-red-600">!=</span> <span className="text-destructive">!=</span>
</SelectItem> </SelectItem>
<SelectItem value=">">&gt;</SelectItem> <SelectItem value=">">&gt;</SelectItem>
<SelectItem value="<">&lt;</SelectItem> <SelectItem value="<">&lt;</SelectItem>
@ -308,11 +308,11 @@ export const DeleteConditionPanel: React.FC<DeleteConditionPanelProps> = ({
<SelectItem value="LIKE">LIKE</SelectItem> <SelectItem value="LIKE">LIKE</SelectItem>
<SelectItem value="IN">IN</SelectItem> <SelectItem value="IN">IN</SelectItem>
<SelectItem value="NOT IN"> <SelectItem value="NOT IN">
<span className="text-red-600">NOT IN</span> <span className="text-destructive">NOT IN</span>
</SelectItem> </SelectItem>
<SelectItem value="EXISTS">EXISTS</SelectItem> <SelectItem value="EXISTS">EXISTS</SelectItem>
<SelectItem value="NOT EXISTS"> <SelectItem value="NOT EXISTS">
<span className="text-red-600">NOT EXISTS</span> <span className="text-destructive">NOT EXISTS</span>
</SelectItem> </SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>

View File

@ -446,9 +446,9 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div> <div>
<div className="font-semibold text-gray-800"> </div> <div className="font-semibold text-gray-800"> </div>
<div className="text-sm text-gray-600"> <div className="text-sm text-muted-foreground">
{toTableColumns.length} {" "} {toTableColumns.length} {" "}
<span className="font-bold text-blue-600"> <span className="font-bold text-primary">
{columnMappings.filter((m) => m.fromColumnName || (m.defaultValue && m.defaultValue.trim())).length} {columnMappings.filter((m) => m.fromColumnName || (m.defaultValue && m.defaultValue.trim())).length}
</span>{" "} </span>{" "}

View File

@ -44,7 +44,7 @@ export const SimpleKeySettings: React.FC<SimpleKeySettingsProps> = ({
{/* 현재 선택된 테이블 표시 */} {/* 현재 선택된 테이블 표시 */}
<div className="mb-4 grid grid-cols-2 gap-4"> <div className="mb-4 grid grid-cols-2 gap-4">
<div> <div>
<Label className="text-xs font-medium text-gray-600">From </Label> <Label className="text-xs font-medium text-muted-foreground">From </Label>
<div className="mt-1"> <div className="mt-1">
<span className="text-sm font-medium text-gray-800"> <span className="text-sm font-medium text-gray-800">
{availableTables.find((t) => t.tableName === selectedFromTable)?.displayName || selectedFromTable} {availableTables.find((t) => t.tableName === selectedFromTable)?.displayName || selectedFromTable}
@ -54,7 +54,7 @@ export const SimpleKeySettings: React.FC<SimpleKeySettingsProps> = ({
</div> </div>
<div> <div>
<Label className="text-xs font-medium text-gray-600">To </Label> <Label className="text-xs font-medium text-muted-foreground">To </Label>
<div className="mt-1"> <div className="mt-1">
<span className="text-sm font-medium text-gray-800"> <span className="text-sm font-medium text-gray-800">
{availableTables.find((t) => t.tableName === selectedToTable)?.displayName || selectedToTable} {availableTables.find((t) => t.tableName === selectedToTable)?.displayName || selectedToTable}
@ -67,7 +67,7 @@ export const SimpleKeySettings: React.FC<SimpleKeySettingsProps> = ({
{/* 컬럼 선택 */} {/* 컬럼 선택 */}
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<Label className="text-xs font-medium text-gray-600">From </Label> <Label className="text-xs font-medium text-muted-foreground">From </Label>
<div className="mt-2 max-h-32 overflow-y-auto rounded border bg-white p-2"> <div className="mt-2 max-h-32 overflow-y-auto rounded border bg-white p-2">
{fromTableColumns.map((column) => ( {fromTableColumns.map((column) => (
<label key={column.columnName} className="flex items-center gap-2 py-1 text-sm"> <label key={column.columnName} className="flex items-center gap-2 py-1 text-sm">
@ -100,7 +100,7 @@ export const SimpleKeySettings: React.FC<SimpleKeySettingsProps> = ({
</div> </div>
<div> <div>
<Label className="text-xs font-medium text-gray-600">To </Label> <Label className="text-xs font-medium text-muted-foreground">To </Label>
<div className="mt-2 max-h-32 overflow-y-auto rounded border bg-white p-2"> <div className="mt-2 max-h-32 overflow-y-auto rounded border bg-white p-2">
{toTableColumns.map((column) => ( {toTableColumns.map((column) => (
<label key={column.columnName} className="flex items-center gap-2 py-1 text-sm"> <label key={column.columnName} className="flex items-center gap-2 py-1 text-sm">
@ -137,7 +137,7 @@ export const SimpleKeySettings: React.FC<SimpleKeySettingsProps> = ({
{(selectedFromColumns.length > 0 || selectedToColumns.length > 0) && ( {(selectedFromColumns.length > 0 || selectedToColumns.length > 0) && (
<div className="mt-4 grid grid-cols-2 gap-4"> <div className="mt-4 grid grid-cols-2 gap-4">
<div> <div>
<Label className="text-xs font-medium text-gray-600"> From </Label> <Label className="text-xs font-medium text-muted-foreground"> From </Label>
<div className="mt-1 flex flex-wrap gap-1"> <div className="mt-1 flex flex-wrap gap-1">
{selectedFromColumns.length > 0 ? ( {selectedFromColumns.length > 0 ? (
selectedFromColumns.map((column) => { selectedFromColumns.map((column) => {
@ -156,7 +156,7 @@ export const SimpleKeySettings: React.FC<SimpleKeySettingsProps> = ({
</div> </div>
<div> <div>
<Label className="text-xs font-medium text-gray-600"> To </Label> <Label className="text-xs font-medium text-muted-foreground"> To </Label>
<div className="mt-1 flex flex-wrap gap-1"> <div className="mt-1 flex flex-wrap gap-1">
{selectedToColumns.length > 0 ? ( {selectedToColumns.length > 0 ? (
selectedToColumns.map((column) => { selectedToColumns.map((column) => {
@ -178,7 +178,7 @@ export const SimpleKeySettings: React.FC<SimpleKeySettingsProps> = ({
</div> </div>
{/* 단순 키값 연결 설정 */} {/* 단순 키값 연결 설정 */}
<div className="rounded-lg border border-l-4 border-l-blue-500 bg-blue-50/30 p-4"> <div className="rounded-lg border border-l-4 border-l-blue-500 bg-accent/30 p-4">
<div className="mb-3 flex items-center gap-2"> <div className="mb-3 flex items-center gap-2">
<Key className="h-4 w-4 text-blue-500" /> <Key className="h-4 w-4 text-blue-500" />
<span className="text-sm font-medium"> </span> <span className="text-sm font-medium"> </span>

View File

@ -37,7 +37,7 @@ export const DataConnectionDesigner: React.FC = () => {
<h1 className="text-2xl font-bold text-gray-900"> <h1 className="text-2xl font-bold text-gray-900">
🎨 - 🎨 -
</h1> </h1>
<p className="text-gray-600 mt-1"> <p className="text-muted-foreground mt-1">
</p> </p>
</div> </div>

View File

@ -51,7 +51,7 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({ connectionType }) =
<> <>
{/* 트랜잭션 설정 - 컴팩트 */} {/* 트랜잭션 설정 - 컴팩트 */}
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-xs font-medium text-gray-600">🔄 </h4> <h4 className="text-xs font-medium text-muted-foreground">🔄 </h4>
<div className="grid grid-cols-3 gap-2"> <div className="grid grid-cols-3 gap-2">
<div> <div>
<Label htmlFor="batchSize" className="text-xs text-gray-500"> <Label htmlFor="batchSize" className="text-xs text-gray-500">
@ -98,7 +98,7 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({ connectionType }) =
<> <>
{/* API 호출 설정 - 컴팩트 */} {/* API 호출 설정 - 컴팩트 */}
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-xs font-medium text-gray-600">🌐 API </h4> <h4 className="text-xs font-medium text-muted-foreground">🌐 API </h4>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
<div> <div>
<Label htmlFor="timeout" className="text-xs text-gray-500"> <Label htmlFor="timeout" className="text-xs text-gray-500">
@ -131,7 +131,7 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({ connectionType }) =
{/* 로깅 설정 - 컴팩트 */} {/* 로깅 설정 - 컴팩트 */}
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-xs font-medium text-gray-600">📝 </h4> <h4 className="text-xs font-medium text-muted-foreground">📝 </h4>
<div> <div>
<Select value={settings.logLevel} onValueChange={(value) => handleSettingChange("logLevel", value)}> <Select value={settings.logLevel} onValueChange={(value) => handleSettingChange("logLevel", value)}>
<SelectTrigger className="h-7 text-xs"> <SelectTrigger className="h-7 text-xs">

View File

@ -49,13 +49,13 @@ export const ConnectionTypeSelector: React.FC<ConnectionTypeSelectorProps> = ({
<div className={`p-2 rounded-lg ${ <div className={`p-2 rounded-lg ${
connectionType === type.id connectionType === type.id
? "bg-orange-100 text-orange-600" ? "bg-orange-100 text-orange-600"
: "bg-gray-100 text-gray-600" : "bg-gray-100 text-muted-foreground"
}`}> }`}>
{type.icon} {type.icon}
</div> </div>
<div> <div>
<h3 className="font-medium text-gray-900">{type.label}</h3> <h3 className="font-medium text-gray-900">{type.label}</h3>
<p className="text-sm text-gray-600">{type.description}</p> <p className="text-sm text-muted-foreground">{type.description}</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -88,9 +88,9 @@ const LeftPanel: React.FC<LeftPanelProps> = ({ state, actions }) => {
{state.connectionType === "external_call" && ( {state.connectionType === "external_call" && (
<> <>
<Separator /> <Separator />
<div className="rounded-md bg-blue-50 p-3"> <div className="rounded-md bg-accent p-3">
<h3 className="mb-1 text-sm font-medium text-blue-800"> </h3> <h3 className="mb-1 text-sm font-medium text-blue-800"> </h3>
<p className="text-xs text-blue-600"> REST API .</p> <p className="text-xs text-primary"> REST API .</p>
</div> </div>
</> </>
)} )}

View File

@ -35,9 +35,9 @@ export const MappingInfoPanel: React.FC<MappingInfoPanelProps> = ({
</div> </div>
</div> </div>
<div className="bg-red-50 p-3 rounded-lg border border-red-200"> <div className="bg-destructive/10 p-3 rounded-lg border border-destructive/20">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<XCircle className="w-4 h-4 text-red-600" /> <XCircle className="w-4 h-4 text-destructive" />
<span className="text-sm font-medium text-red-800"> </span> <span className="text-sm font-medium text-red-800"> </span>
</div> </div>
<div className="text-2xl font-bold text-red-900 mt-1"> <div className="text-2xl font-bold text-red-900 mt-1">
@ -45,9 +45,9 @@ export const MappingInfoPanel: React.FC<MappingInfoPanelProps> = ({
</div> </div>
</div> </div>
<div className="bg-blue-50 p-3 rounded-lg border border-blue-200"> <div className="bg-accent p-3 rounded-lg border border-primary/20">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Database className="w-4 h-4 text-blue-600" /> <Database className="w-4 h-4 text-primary" />
<span className="text-sm font-medium text-blue-800"> </span> <span className="text-sm font-medium text-blue-800"> </span>
</div> </div>
<div className="text-2xl font-bold text-blue-900 mt-1"> <div className="text-2xl font-bold text-blue-900 mt-1">
@ -88,7 +88,7 @@ export const MappingInfoPanel: React.FC<MappingInfoPanelProps> = ({
? "border-orange-500 bg-orange-50" ? "border-orange-500 bg-orange-50"
: mapping.isValid : mapping.isValid
? "border-green-200 bg-green-50 hover:border-green-300" ? "border-green-200 bg-green-50 hover:border-green-300"
: "border-red-200 bg-red-50 hover:border-red-300" : "border-destructive/20 bg-destructive/10 hover:border-red-300"
}`} }`}
onClick={() => onMappingSelect(mapping.id)} onClick={() => onMappingSelect(mapping.id)}
> >
@ -126,13 +126,13 @@ export const MappingInfoPanel: React.FC<MappingInfoPanelProps> = ({
{mapping.isValid ? ( {mapping.isValid ? (
<CheckCircle className="w-4 h-4 text-green-600" /> <CheckCircle className="w-4 h-4 text-green-600" />
) : ( ) : (
<XCircle className="w-4 h-4 text-red-600" /> <XCircle className="w-4 h-4 text-destructive" />
)} )}
</div> </div>
</div> </div>
{mapping.validationMessage && ( {mapping.validationMessage && (
<p className="text-xs text-red-600 mt-1"> <p className="text-xs text-destructive mt-1">
{mapping.validationMessage} {mapping.validationMessage}
</p> </p>
)} )}

View File

@ -273,7 +273,7 @@ const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
.map((column) => ( .map((column) => (
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}> <SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-blue-600">📤</span> <span className="text-primary">📤</span>
<span>{column.displayName || column.columnName}</span> <span>{column.displayName || column.columnName}</span>
<Badge variant="outline" className="text-xs"> <Badge variant="outline" className="text-xs">
{column.webType || column.dataType} {column.webType || column.dataType}
@ -359,7 +359,7 @@ const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
{/* 선택된 날짜 타입에 대한 설명 */} {/* 선택된 날짜 타입에 대한 설명 */}
{mapping.value?.startsWith("#") && mapping.value !== "#custom" && ( {mapping.value?.startsWith("#") && mapping.value !== "#custom" && (
<div className="text-muted-foreground rounded bg-blue-50 p-2 text-xs"> <div className="text-muted-foreground rounded bg-accent p-2 text-xs">
{mapping.value === "#NOW" && "⏰ 현재 날짜와 시간이 저장됩니다"} {mapping.value === "#NOW" && "⏰ 현재 날짜와 시간이 저장됩니다"}
{mapping.value === "#TODAY" && "📅 현재 날짜 (00:00:00)가 저장됩니다"} {mapping.value === "#TODAY" && "📅 현재 날짜 (00:00:00)가 저장됩니다"}
{mapping.value === "#YESTERDAY" && "📅 어제 날짜가 저장됩니다"} {mapping.value === "#YESTERDAY" && "📅 어제 날짜가 저장됩니다"}
@ -497,7 +497,7 @@ const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
.map((column) => ( .map((column) => (
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}> <SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-blue-600">📤</span> <span className="text-primary">📤</span>
<span>{column.displayName || column.columnName}</span> <span>{column.displayName || column.columnName}</span>
</div> </div>
</SelectItem> </SelectItem>
@ -625,7 +625,7 @@ const ActionConditionBuilder: React.FC<ActionConditionBuilderProps> = ({
.map((column) => ( .map((column) => (
<SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}> <SelectItem key={`from_${column.columnName}`} value={`from.${column.columnName}`}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-blue-600">📤</span> <span className="text-primary">📤</span>
<span>{column.displayName || column.columnName}</span> <span>{column.displayName || column.columnName}</span>
</div> </div>
</SelectItem> </SelectItem>

View File

@ -80,7 +80,7 @@ export const ConnectionStep: React.FC<ConnectionStepProps> = ({
<h2 className="text-2xl font-bold text-gray-900 mb-2"> <h2 className="text-2xl font-bold text-gray-900 mb-2">
</h2> </h2>
<p className="text-gray-600"> <p className="text-muted-foreground">
</p> </p>
</div> </div>
@ -89,8 +89,8 @@ export const ConnectionStep: React.FC<ConnectionStepProps> = ({
{/* FROM 연결 */} {/* FROM 연결 */}
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center"> <div className="w-8 h-8 bg-primary/20 rounded-full flex items-center justify-center">
<span className="text-blue-600 font-bold">1</span> <span className="text-primary font-bold">1</span>
</div> </div>
<h3 className="text-lg font-semibold text-gray-900">FROM </h3> <h3 className="text-lg font-semibold text-gray-900">FROM </h3>
<span className="text-sm text-gray-500">( )</span> <span className="text-sm text-gray-500">( )</span>
@ -102,20 +102,20 @@ export const ConnectionStep: React.FC<ConnectionStepProps> = ({
key={connection.id} key={connection.id}
className={`p-4 rounded-lg border-2 cursor-pointer transition-all duration-200 ${ className={`p-4 rounded-lg border-2 cursor-pointer transition-all duration-200 ${
selectedFrom === connection.id selectedFrom === connection.id
? "border-blue-500 bg-blue-50 shadow-md" ? "border-primary bg-accent shadow-md"
: "border-gray-200 bg-white hover:border-blue-300 hover:bg-blue-25" : "border-gray-200 bg-white hover:border-blue-300 hover:bg-blue-25"
}`} }`}
onClick={() => handleFromSelect(connection.id)} onClick={() => handleFromSelect(connection.id)}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Database className="w-6 h-6 text-blue-600" /> <Database className="w-6 h-6 text-primary" />
<div className="flex-1"> <div className="flex-1">
<h4 className="font-medium text-gray-900">{connection.name}</h4> <h4 className="font-medium text-gray-900">{connection.name}</h4>
<p className="text-sm text-gray-600">{connection.type}</p> <p className="text-sm text-muted-foreground">{connection.type}</p>
<p className="text-xs text-gray-500">{connection.host}:{connection.port}</p> <p className="text-xs text-gray-500">{connection.host}:{connection.port}</p>
</div> </div>
{selectedFrom === connection.id && ( {selectedFrom === connection.id && (
<CheckCircle className="w-5 h-5 text-blue-600" /> <CheckCircle className="w-5 h-5 text-primary" />
)} )}
</div> </div>
</div> </div>
@ -155,7 +155,7 @@ export const ConnectionStep: React.FC<ConnectionStepProps> = ({
<Database className="w-6 h-6 text-green-600" /> <Database className="w-6 h-6 text-green-600" />
<div className="flex-1"> <div className="flex-1">
<h4 className="font-medium text-gray-900">{connection.name}</h4> <h4 className="font-medium text-gray-900">{connection.name}</h4>
<p className="text-sm text-gray-600">{connection.type}</p> <p className="text-sm text-muted-foreground">{connection.type}</p>
<p className="text-xs text-gray-500">{connection.host}:{connection.port}</p> <p className="text-xs text-gray-500">{connection.host}:{connection.port}</p>
</div> </div>
{selectedTo === connection.id && ( {selectedTo === connection.id && (

View File

@ -148,7 +148,7 @@ const ControlConditionStep: React.FC<ControlConditionStepProps> = ({ state, acti
<CardContent className="flex h-full flex-col overflow-hidden p-0"> <CardContent className="flex h-full flex-col overflow-hidden p-0">
<div className="min-h-0 flex-1 space-y-6 overflow-y-auto p-4"> <div className="min-h-0 flex-1 space-y-6 overflow-y-auto p-4">
{/* 제어 실행 조건 안내 */} {/* 제어 실행 조건 안내 */}
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4"> <div className="rounded-lg border border-primary/20 bg-accent p-4">
<h4 className="mb-2 text-sm font-medium text-blue-800"> ?</h4> <h4 className="mb-2 text-sm font-medium text-blue-800"> ?</h4>
<div className="space-y-1 text-sm text-blue-700"> <div className="space-y-1 text-sm text-blue-700">
<p> <p>
@ -363,7 +363,7 @@ const ControlConditionStep: React.FC<ControlConditionStepProps> = ({ state, acti
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => actions.deleteControlCondition(index)} onClick={() => actions.deleteControlCondition(index)}
className="text-red-600 hover:text-red-700" className="text-destructive hover:text-red-700"
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" />
</Button> </Button>

View File

@ -71,14 +71,14 @@ export const FieldMappingStep: React.FC<FieldMappingStepProps> = ({
<h2 className="text-2xl font-bold text-gray-900 mb-2"> <h2 className="text-2xl font-bold text-gray-900 mb-2">
</h2> </h2>
<p className="text-gray-600"> <p className="text-muted-foreground">
</p> </p>
</div> </div>
{/* 매핑 통계 */} {/* 매핑 통계 */}
<div className="grid grid-cols-4 gap-4"> <div className="grid grid-cols-4 gap-4">
<div className="bg-blue-50 p-4 rounded-lg border border-blue-200"> <div className="bg-accent p-4 rounded-lg border border-primary/20">
<div className="text-2xl font-bold text-blue-900">{fieldMappings.length}</div> <div className="text-2xl font-bold text-blue-900">{fieldMappings.length}</div>
<div className="text-sm text-blue-700"> </div> <div className="text-sm text-blue-700"> </div>
</div> </div>
@ -88,7 +88,7 @@ export const FieldMappingStep: React.FC<FieldMappingStepProps> = ({
</div> </div>
<div className="text-sm text-green-700"> </div> <div className="text-sm text-green-700"> </div>
</div> </div>
<div className="bg-red-50 p-4 rounded-lg border border-red-200"> <div className="bg-destructive/10 p-4 rounded-lg border border-destructive/20">
<div className="text-2xl font-bold text-red-900"> <div className="text-2xl font-bold text-red-900">
{fieldMappings.filter(m => !m.isValid).length} {fieldMappings.filter(m => !m.isValid).length}
</div> </div>
@ -107,8 +107,8 @@ export const FieldMappingStep: React.FC<FieldMappingStepProps> = ({
{/* FROM 테이블 필드들 */} {/* FROM 테이블 필드들 */}
<div className="space-y-4"> <div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 flex items-center gap-2"> <h3 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
<div className="w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center"> <div className="w-6 h-6 bg-primary/20 rounded-full flex items-center justify-center">
<span className="text-blue-600 font-bold text-sm">FROM</span> <span className="text-primary font-bold text-sm">FROM</span>
</div> </div>
{fromTable?.name} {fromTable?.name}
</h3> </h3>
@ -122,13 +122,13 @@ export const FieldMappingStep: React.FC<FieldMappingStepProps> = ({
className={`p-3 rounded-lg border-2 cursor-move transition-all duration-200 ${ className={`p-3 rounded-lg border-2 cursor-move transition-all duration-200 ${
isFieldMapped(field.name) isFieldMapped(field.name)
? "border-green-300 bg-green-50 opacity-60" ? "border-green-300 bg-green-50 opacity-60"
: "border-blue-200 bg-blue-50 hover:border-blue-400 hover:bg-blue-100" : "border-primary/20 bg-accent hover:border-blue-400 hover:bg-primary/20"
}`} }`}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<div className="font-medium text-gray-900">{field.name}</div> <div className="font-medium text-gray-900">{field.name}</div>
<div className="text-sm text-gray-600">{field.type}</div> <div className="text-sm text-muted-foreground">{field.type}</div>
{field.primaryKey && ( {field.primaryKey && (
<span className="text-xs bg-yellow-100 text-yellow-800 px-2 py-1 rounded"> <span className="text-xs bg-yellow-100 text-yellow-800 px-2 py-1 rounded">
PK PK
@ -170,7 +170,7 @@ export const FieldMappingStep: React.FC<FieldMappingStepProps> = ({
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<div className="font-medium text-gray-900">{field.name}</div> <div className="font-medium text-gray-900">{field.name}</div>
<div className="text-sm text-gray-600">{field.type}</div> <div className="text-sm text-muted-foreground">{field.type}</div>
{field.primaryKey && ( {field.primaryKey && (
<span className="text-xs bg-yellow-100 text-yellow-800 px-2 py-1 rounded"> <span className="text-xs bg-yellow-100 text-yellow-800 px-2 py-1 rounded">
PK PK
@ -196,7 +196,7 @@ export const FieldMappingStep: React.FC<FieldMappingStepProps> = ({
{fieldMappings.find(m => m.toField.name === field.name)?.isValid ? ( {fieldMappings.find(m => m.toField.name === field.name)?.isValid ? (
<CheckCircle className="w-5 h-5 text-green-600" /> <CheckCircle className="w-5 h-5 text-green-600" />
) : ( ) : (
<AlertCircle className="w-5 h-5 text-red-600" /> <AlertCircle className="w-5 h-5 text-destructive" />
)} )}
</div> </div>
)} )}
@ -213,7 +213,7 @@ export const FieldMappingStep: React.FC<FieldMappingStepProps> = ({
<div className="flex justify-between"> <div className="flex justify-between">
<button <button
onClick={onBack} onClick={onBack}
className="flex items-center gap-2 px-6 py-3 rounded-lg font-medium text-gray-600 bg-gray-100 hover:bg-gray-200 transition-all duration-200" className="flex items-center gap-2 px-6 py-3 rounded-lg font-medium text-muted-foreground bg-gray-100 hover:bg-gray-200 transition-all duration-200"
> >
<ArrowLeft className="w-4 h-4" /> <ArrowLeft className="w-4 h-4" />

View File

@ -150,7 +150,7 @@ const MultiActionConfigStep: React.FC<MultiActionConfigStepProps> = ({
const getLogicalOperatorColor = (operator: string) => { const getLogicalOperatorColor = (operator: string) => {
switch (operator) { switch (operator) {
case "AND": case "AND":
return "bg-blue-100 text-blue-800"; return "bg-primary/20 text-blue-800";
case "OR": case "OR":
return "bg-orange-100 text-orange-800"; return "bg-orange-100 text-orange-800";
default: default:
@ -271,7 +271,7 @@ const MultiActionConfigStep: React.FC<MultiActionConfigStepProps> = ({
{/* 그룹 간 논리 연산자 선택 */} {/* 그룹 간 논리 연산자 선택 */}
{actionGroups.length > 1 && ( {actionGroups.length > 1 && (
<div className="rounded-md border bg-blue-50 p-3"> <div className="rounded-md border bg-accent p-3">
<div className="mb-2 flex items-center gap-2"> <div className="mb-2 flex items-center gap-2">
<h4 className="text-sm font-medium text-blue-900"> </h4> <h4 className="text-sm font-medium text-blue-900"> </h4>
</div> </div>
@ -649,9 +649,9 @@ const MultiActionConfigStep: React.FC<MultiActionConfigStepProps> = ({
</div> </div>
{/* 그룹 로직 설명 */} {/* 그룹 로직 설명 */}
<div className="mt-4 rounded-md bg-blue-50 p-3"> <div className="mt-4 rounded-md bg-accent p-3">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<AlertTriangle className="mt-0.5 h-4 w-4 text-blue-600" /> <AlertTriangle className="mt-0.5 h-4 w-4 text-primary" />
<div className="text-sm"> <div className="text-sm">
<div className="font-medium text-blue-900">{group.logicalOperator} </div> <div className="font-medium text-blue-900">{group.logicalOperator} </div>
<div className="text-blue-700"> <div className="text-blue-700">

View File

@ -71,7 +71,7 @@ const RightPanel: React.FC<RightPanelProps> = ({ state, actions }) => {
{/* 헤더 */} {/* 헤더 */}
<div className="flex-shrink-0 px-4 py-2"> <div className="flex-shrink-0 px-4 py-2">
<div className="flex items-center gap-3 border-b pb-2"> <div className="flex items-center gap-3 border-b pb-2">
<Globe className="h-5 w-5 text-blue-600" /> <Globe className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold"> </h2> <h2 className="text-lg font-semibold"> </h2>
</div> </div>
<p className="text-muted-foreground mt-1 text-sm"> <p className="text-muted-foreground mt-1 text-sm">
@ -89,7 +89,7 @@ const RightPanel: React.FC<RightPanelProps> = ({ state, actions }) => {
value={state.relationshipName || ""} value={state.relationshipName || ""}
onChange={(e) => actions.setRelationshipName(e.target.value)} onChange={(e) => actions.setRelationshipName(e.target.value)}
placeholder="외부호출 관계의 이름을 입력하세요" placeholder="외부호출 관계의 이름을 입력하세요"
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none" className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:ring-1 focus:ring-blue-500 focus:outline-none"
/> />
</div> </div>
<div> <div>
@ -99,7 +99,7 @@ const RightPanel: React.FC<RightPanelProps> = ({ state, actions }) => {
onChange={(e) => actions.setDescription(e.target.value)} onChange={(e) => actions.setDescription(e.target.value)}
placeholder="외부호출의 용도나 설명을 입력하세요" placeholder="외부호출의 용도나 설명을 입력하세요"
rows={2} rows={2}
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none" className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:ring-1 focus:ring-blue-500 focus:outline-none"
/> />
</div> </div>
</div> </div>

View File

@ -35,7 +35,7 @@ export const StepProgress: React.FC<StepProgressProps> = ({
? "bg-green-500 text-white" ? "bg-green-500 text-white"
: step.id === currentStep : step.id === currentStep
? "bg-orange-500 text-white" ? "bg-orange-500 text-white"
: "bg-gray-200 text-gray-600" : "bg-gray-200 text-muted-foreground"
}`} }`}
> >
{step.id < currentStep ? ( {step.id < currentStep ? (
@ -52,7 +52,7 @@ export const StepProgress: React.FC<StepProgressProps> = ({
{step.title} {step.title}
</h3> </h3>
<p className={`text-xs ${ <p className={`text-xs ${
step.id <= currentStep ? "text-gray-600" : "text-gray-400" step.id <= currentStep ? "text-muted-foreground" : "text-gray-400"
}`}> }`}>
{step.description} {step.description}
</p> </p>

View File

@ -90,7 +90,7 @@ export const TableStep: React.FC<TableStepProps> = ({
<h2 className="text-2xl font-bold text-gray-900 mb-2"> <h2 className="text-2xl font-bold text-gray-900 mb-2">
</h2> </h2>
<p className="text-gray-600"> <p className="text-muted-foreground">
</p> </p>
</div> </div>
@ -99,7 +99,7 @@ export const TableStep: React.FC<TableStepProps> = ({
<div className="bg-gray-50 rounded-lg p-4"> <div className="bg-gray-50 rounded-lg p-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Database className="w-5 h-5 text-blue-600" /> <Database className="w-5 h-5 text-primary" />
<span className="font-medium text-gray-900">{fromConnection?.name}</span> <span className="font-medium text-gray-900">{fromConnection?.name}</span>
<span className="text-sm text-gray-500"></span> <span className="text-sm text-gray-500"></span>
<Database className="w-5 h-5 text-green-600" /> <Database className="w-5 h-5 text-green-600" />
@ -112,8 +112,8 @@ export const TableStep: React.FC<TableStepProps> = ({
{/* FROM 테이블 */} {/* FROM 테이블 */}
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center"> <div className="w-8 h-8 bg-primary/20 rounded-full flex items-center justify-center">
<span className="text-blue-600 font-bold">1</span> <span className="text-primary font-bold">1</span>
</div> </div>
<h3 className="text-lg font-semibold text-gray-900"> </h3> <h3 className="text-lg font-semibold text-gray-900"> </h3>
<span className="text-sm text-gray-500">(FROM)</span> <span className="text-sm text-gray-500">(FROM)</span>
@ -125,20 +125,20 @@ export const TableStep: React.FC<TableStepProps> = ({
key={table.name} key={table.name}
className={`p-4 rounded-lg border-2 cursor-pointer transition-all duration-200 ${ className={`p-4 rounded-lg border-2 cursor-pointer transition-all duration-200 ${
selectedFromTable === table.name selectedFromTable === table.name
? "border-blue-500 bg-blue-50 shadow-md" ? "border-primary bg-accent shadow-md"
: "border-gray-200 bg-white hover:border-blue-300 hover:bg-blue-25" : "border-gray-200 bg-white hover:border-blue-300 hover:bg-blue-25"
}`} }`}
onClick={() => handleFromTableSelect(table.name)} onClick={() => handleFromTableSelect(table.name)}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Table className="w-6 h-6 text-blue-600" /> <Table className="w-6 h-6 text-primary" />
<div className="flex-1"> <div className="flex-1">
<h4 className="font-medium text-gray-900">{table.name}</h4> <h4 className="font-medium text-gray-900">{table.name}</h4>
<p className="text-sm text-gray-600">{table.columns.length} </p> <p className="text-sm text-muted-foreground">{table.columns.length} </p>
<p className="text-xs text-gray-500">{table.rowCount?.toLocaleString()} </p> <p className="text-xs text-gray-500">{table.rowCount?.toLocaleString()} </p>
</div> </div>
{selectedFromTable === table.name && ( {selectedFromTable === table.name && (
<CheckCircle className="w-5 h-5 text-blue-600" /> <CheckCircle className="w-5 h-5 text-primary" />
)} )}
</div> </div>
</div> </div>
@ -171,7 +171,7 @@ export const TableStep: React.FC<TableStepProps> = ({
<Table className="w-6 h-6 text-green-600" /> <Table className="w-6 h-6 text-green-600" />
<div className="flex-1"> <div className="flex-1">
<h4 className="font-medium text-gray-900">{table.name}</h4> <h4 className="font-medium text-gray-900">{table.name}</h4>
<p className="text-sm text-gray-600">{table.columns.length} </p> <p className="text-sm text-muted-foreground">{table.columns.length} </p>
<p className="text-xs text-gray-500">{table.rowCount?.toLocaleString()} </p> <p className="text-xs text-gray-500">{table.rowCount?.toLocaleString()} </p>
</div> </div>
{selectedToTable === table.name && ( {selectedToTable === table.name && (
@ -188,7 +188,7 @@ export const TableStep: React.FC<TableStepProps> = ({
<div className="flex justify-between"> <div className="flex justify-between">
<button <button
onClick={onBack} onClick={onBack}
className="flex items-center gap-2 px-6 py-3 rounded-lg font-medium text-gray-600 bg-gray-100 hover:bg-gray-200 transition-all duration-200" className="flex items-center gap-2 px-6 py-3 rounded-lg font-medium text-muted-foreground bg-gray-100 hover:bg-gray-200 transition-all duration-200"
> >
<ArrowLeft className="w-4 h-4" /> <ArrowLeft className="w-4 h-4" />

View File

@ -128,9 +128,9 @@ const FieldColumn: React.FC<FieldColumnProps> = ({
: isMapped : isMapped
? "border-green-500 bg-green-50 shadow-sm" ? "border-green-500 bg-green-50 shadow-sm"
: isBlockedDropTarget : isBlockedDropTarget
? "border-red-400 bg-red-50 shadow-md" ? "border-red-400 bg-destructive/10 shadow-md"
: isDropTarget : isDropTarget
? "border-blue-400 bg-blue-50 shadow-md" ? "border-blue-400 bg-accent shadow-md"
: "border-border hover:bg-muted/50 hover:shadow-sm" : "border-border hover:bg-muted/50 hover:shadow-sm"
} `} } `}
draggable={type === "from" && !isMapped} draggable={type === "from" && !isMapped}

View File

@ -311,7 +311,7 @@ const FieldMappingCanvas: React.FC<FieldMappingCanvasProps> = ({
</div> </div>
{/* 매핑 규칙 안내 */} {/* 매핑 규칙 안내 */}
<div className="mt-4 rounded-lg border border-blue-200 bg-blue-50 p-3"> <div className="mt-4 rounded-lg border border-primary/20 bg-accent p-3">
<h4 className="mb-2 text-sm font-medium">📋 </h4> <h4 className="mb-2 text-sm font-medium">📋 </h4>
<div className="text-muted-foreground space-y-1 text-xs"> <div className="text-muted-foreground space-y-1 text-xs">
<p> 1:N ( )</p> <p> 1:N ( )</p>

View File

@ -411,7 +411,7 @@ const ExternalCallTestPanel: React.FC<ExternalCallTestPanelProps> = ({
{testResult.responseTime !== undefined && ( {testResult.responseTime !== undefined && (
<div> <div>
<Label className="text-muted-foreground text-xs"> </Label> <Label className="text-muted-foreground text-xs"> </Label>
<div className="mt-1 rounded border bg-blue-50 p-2"> <div className="mt-1 rounded border bg-accent p-2">
<span className="font-mono text-sm text-blue-700">{testResult.responseTime}ms</span> <span className="font-mono text-sm text-blue-700">{testResult.responseTime}ms</span>
</div> </div>
</div> </div>

View File

@ -272,7 +272,7 @@ const RestApiSettings: React.FC<RestApiSettingsProps> = ({ settings, onSettingsC
value={settings.apiUrl} value={settings.apiUrl}
onChange={(e) => handleUrlChange(e.target.value)} onChange={(e) => handleUrlChange(e.target.value)}
disabled={readonly} disabled={readonly}
className={validationErrors.some((e) => e.includes("URL")) ? "border-red-500" : ""} className={validationErrors.some((e) => e.includes("URL")) ? "border-destructive" : ""}
/> />
<div className="text-muted-foreground text-xs"> API의 URL을 .</div> <div className="text-muted-foreground text-xs"> API의 URL을 .</div>
</div> </div>

View File

@ -324,7 +324,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
<div <div
className={`group flex h-10 cursor-pointer items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-200 ease-in-out ${ className={`group flex h-10 cursor-pointer items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-200 ease-in-out ${
pathname === menu.url pathname === menu.url
? "border-l-4 border-blue-500 bg-gradient-to-br from-slate-100 to-blue-100/40 text-slate-900" ? "border-l-4 border-primary bg-gradient-to-br from-slate-100 to-blue-100/40 text-slate-900"
: isExpanded : isExpanded
? "bg-slate-100 text-slate-900" ? "bg-slate-100 text-slate-900"
: "text-slate-600 hover:bg-slate-50 hover:text-slate-900" : "text-slate-600 hover:bg-slate-50 hover:text-slate-900"
@ -352,7 +352,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
key={child.id} key={child.id}
className={`flex cursor-pointer items-center rounded-lg px-3 py-2 text-sm transition-colors hover:cursor-pointer ${ className={`flex cursor-pointer items-center rounded-lg px-3 py-2 text-sm transition-colors hover:cursor-pointer ${
pathname === child.url pathname === child.url
? "border-l-4 border-blue-500 bg-gradient-to-br from-slate-100 to-blue-100/40 text-slate-900" ? "border-l-4 border-primary bg-gradient-to-br from-slate-100 to-blue-100/40 text-slate-900"
: "text-slate-600 hover:bg-slate-50 hover:text-slate-900" : "text-slate-600 hover:bg-slate-50 hover:text-slate-900"
}`} }`}
onClick={() => handleMenuClick(child)} onClick={() => handleMenuClick(child)}
@ -376,7 +376,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
return ( return (
<div className="flex h-screen items-center justify-center"> <div className="flex h-screen items-center justify-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-blue-500 border-t-transparent"></div> <div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"></div>
<p>...</p> <p>...</p>
</div> </div>
</div> </div>
@ -423,7 +423,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
className={`flex w-full items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-200 hover:cursor-pointer ${ className={`flex w-full items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors duration-200 hover:cursor-pointer ${
isAdminMode isAdminMode
? "border border-orange-200 bg-orange-50 text-orange-700 hover:bg-orange-100" ? "border border-orange-200 bg-orange-50 text-orange-700 hover:bg-orange-100"
: "border border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100" : "border border-primary/20 bg-accent text-blue-700 hover:bg-primary/20"
}`} }`}
> >
{isAdminMode ? ( {isAdminMode ? (
@ -486,7 +486,7 @@ export function AppLayout({ children }: AppLayoutProps) {
fallback={ fallback={
<div className="flex h-screen items-center justify-center"> <div className="flex h-screen items-center justify-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-blue-500 border-t-transparent"></div> <div className="mb-4 h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"></div>
<p>...</p> <p>...</p>
</div> </div>
</div> </div>

View File

@ -22,9 +22,9 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
case "success": case "success":
return "text-green-600"; return "text-green-600";
case "error": case "error":
return "text-red-600"; return "text-destructive";
default: default:
return "text-blue-600"; return "text-primary";
} }
}; };
@ -35,7 +35,7 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
<DialogTitle className={getTypeColor()}>{title}</DialogTitle> <DialogTitle className={getTypeColor()}>{title}</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="py-4"> <div className="py-4">
<p className="text-sm text-gray-600">{message}</p> <p className="text-sm text-muted-foreground">{message}</p>
</div> </div>
<div className="flex justify-end"> <div className="flex justify-end">
<Button onClick={onClose} className="w-20"> <Button onClick={onClose} className="w-20">

View File

@ -49,7 +49,7 @@ export default function ConfirmDeleteModal({
<div className="p-6 space-y-4"> <div className="p-6 space-y-4">
<p className="text-gray-700">{message}</p> <p className="text-gray-700">{message}</p>
{itemName && ( {itemName && (
<div className="bg-red-50 border border-red-200 rounded-lg p-3"> <div className="bg-destructive/10 border border-destructive/20 rounded-lg p-3">
<p className="text-sm font-medium text-red-800"> <p className="text-sm font-medium text-red-800">
: <span className="font-bold">{itemName}</span> : <span className="font-bold">{itemName}</span>
</p> </p>
@ -71,7 +71,8 @@ export default function ConfirmDeleteModal({
</Button> </Button>
<Button <Button
onClick={handleConfirm} onClick={handleConfirm}
className="flex-1 bg-red-500 hover:bg-red-600" variant="destructive"
className="flex-1"
> >
</Button> </Button>

View File

@ -332,7 +332,8 @@ export default function MailAccountModal({
type="button" type="button"
onClick={handleTestConnection} onClick={handleTestConnection}
disabled={isTesting} disabled={isTesting}
className="w-full bg-blue-500 hover:bg-blue-600" variant="default"
className="w-full"
> >
{isTesting ? ( {isTesting ? (
<> <>
@ -351,14 +352,14 @@ export default function MailAccountModal({
<div <div
className={`mt-3 p-3 rounded-lg flex items-start gap-2 ${ className={`mt-3 p-3 rounded-lg flex items-start gap-2 ${
testResult.success testResult.success
? 'bg-green-50 border border-green-200' ? 'bg-green-50 border border-green-500/20'
: 'bg-red-50 border border-red-200' : 'bg-destructive/10 border border-destructive/20'
}`} }`}
> >
{testResult.success ? ( {testResult.success ? (
<CheckCircle2 className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" /> <CheckCircle2 className="w-5 h-5 text-green-600 flex-shrink-0 mt-0.5" />
) : ( ) : (
<AlertCircle className="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5" /> <AlertCircle className="w-5 h-5 text-destructive flex-shrink-0 mt-0.5" />
)} )}
<p <p
className={`text-sm ${ className={`text-sm ${
@ -385,7 +386,8 @@ export default function MailAccountModal({
</Button> </Button>
<Button <Button
type="submit" type="submit"
className="flex-1 bg-orange-500 hover:bg-orange-600" variant="default"
className="flex-1"
disabled={isSubmitting} disabled={isSubmitting}
> >
{isSubmitting ? ( {isSubmitting ? (

View File

@ -21,6 +21,7 @@ interface MailAccountTableProps {
onEdit: (account: MailAccount) => void; onEdit: (account: MailAccount) => void;
onDelete: (account: MailAccount) => void; onDelete: (account: MailAccount) => void;
onToggleStatus: (account: MailAccount) => void; onToggleStatus: (account: MailAccount) => void;
onTestConnection: (account: MailAccount) => void;
} }
export default function MailAccountTable({ export default function MailAccountTable({
@ -28,6 +29,7 @@ export default function MailAccountTable({
onEdit, onEdit,
onDelete, onDelete,
onToggleStatus, onToggleStatus,
onTestConnection,
}: MailAccountTableProps) { }: MailAccountTableProps) {
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [sortField, setSortField] = useState<keyof MailAccount>('createdAt'); const [sortField, setSortField] = useState<keyof MailAccount>('createdAt');
@ -82,7 +84,7 @@ export default function MailAccountTable({
return ( return (
<div className="bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl p-12 text-center border-2 border-dashed border-gray-300"> <div className="bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl p-12 text-center border-2 border-dashed border-gray-300">
<Mail className="w-16 h-16 text-gray-400 mx-auto mb-4" /> <Mail className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<p className="text-lg font-medium text-gray-600 mb-2"> <p className="text-lg font-medium text-muted-foreground mb-2">
</p> </p>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
@ -174,10 +176,10 @@ export default function MailAccountTable({
<div className="font-medium text-gray-900">{account.name}</div> <div className="font-medium text-gray-900">{account.name}</div>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="text-sm text-gray-600">{account.email}</div> <div className="text-sm text-muted-foreground">{account.email}</div>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="text-sm text-gray-600"> <div className="text-sm text-muted-foreground">
{account.smtpHost}:{account.smtpPort} {account.smtpHost}:{account.smtpPort}
</div> </div>
<div className="text-xs text-gray-400"> <div className="text-xs text-gray-400">
@ -190,7 +192,7 @@ export default function MailAccountTable({
className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all hover:scale-105 ${ className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all hover:scale-105 ${
account.status === 'active' account.status === 'active'
? 'bg-green-100 text-green-700 hover:bg-green-200' ? 'bg-green-100 text-green-700 hover:bg-green-200'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200' : 'bg-gray-100 text-muted-foreground hover:bg-gray-200'
}`} }`}
> >
{account.status === 'active' ? ( {account.status === 'active' ? (
@ -214,22 +216,29 @@ export default function MailAccountTable({
</div> </div>
</td> </td>
<td className="px-6 py-4 text-center"> <td className="px-6 py-4 text-center">
<div className="text-sm text-gray-600"> <div className="text-sm text-muted-foreground">
{formatDate(account.createdAt)} {formatDate(account.createdAt)}
</div> </div>
</td> </td>
<td className="px-6 py-4"> <td className="px-6 py-4">
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
<button <button
onClick={() => onEdit(account)} onClick={() => onTestConnection(account)}
className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors" className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
title="SMTP 연결 테스트"
>
<Zap className="w-4 h-4" />
</button>
<button
onClick={() => onEdit(account)}
className="p-2 text-primary hover:bg-accent rounded-lg transition-colors"
title="수정" title="수정"
> >
<Edit2 className="w-4 h-4" /> <Edit2 className="w-4 h-4" />
</button> </button>
<button <button
onClick={() => onDelete(account)} onClick={() => onDelete(account)}
className="p-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors" className="p-2 text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
title="삭제" title="삭제"
> >
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />
@ -244,7 +253,7 @@ export default function MailAccountTable({
</div> </div>
{/* 결과 요약 */} {/* 결과 요약 */}
<div className="text-sm text-gray-600 text-center"> <div className="text-sm text-muted-foreground text-center">
{accounts.length} {sortedAccounts.length} {accounts.length} {sortedAccounts.length}
{searchTerm && ` (검색: "${searchTerm}")`} {searchTerm && ` (검색: "${searchTerm}")`}
</div> </div>

View File

@ -63,7 +63,7 @@ export default function MailDesigner({
// 컴포넌트 타입 정의 // 컴포넌트 타입 정의
const componentTypes = [ const componentTypes = [
{ type: "text", icon: Type, label: "텍스트", color: "bg-blue-100 hover:bg-blue-200" }, { type: "text", icon: Type, label: "텍스트", color: "bg-primary/20 hover:bg-blue-200" },
{ type: "button", icon: MousePointer, label: "버튼", color: "bg-green-100 hover:bg-green-200" }, { type: "button", icon: MousePointer, label: "버튼", color: "bg-green-100 hover:bg-green-200" },
{ type: "image", icon: ImageIcon, label: "이미지", color: "bg-purple-100 hover:bg-purple-200" }, { type: "image", icon: ImageIcon, label: "이미지", color: "bg-purple-100 hover:bg-purple-200" },
{ type: "spacer", icon: Square, label: "여백", color: "bg-gray-100 hover:bg-gray-200" }, { type: "spacer", icon: Square, label: "여백", color: "bg-gray-100 hover:bg-gray-200" },
@ -201,7 +201,7 @@ export default function MailDesigner({
<Eye className="w-4 h-4 mr-2" /> <Eye className="w-4 h-4 mr-2" />
</Button> </Button>
<Button onClick={handleSend} className="w-full bg-orange-500 hover:bg-orange-600 text-white"> <Button onClick={handleSend} variant="default" className="w-full">
<Send className="w-4 h-4 mr-2" /> <Send className="w-4 h-4 mr-2" />
</Button> </Button>
@ -253,7 +253,7 @@ export default function MailDesigner({
e.stopPropagation(); e.stopPropagation();
removeComponent(comp.id); removeComponent(comp.id);
}} }}
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity bg-red-500 text-white rounded-full p-1 hover:bg-red-600" className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity bg-destructive text-white rounded-full p-1 hover:bg-destructive/90"
> >
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />
</button> </button>

View File

@ -168,12 +168,12 @@ export default function MailDetailModal({
{loading ? ( {loading ? (
<div className="flex justify-center items-center py-16"> <div className="flex justify-center items-center py-16">
<Loader2 className="w-8 h-8 animate-spin text-orange-500" /> <Loader2 className="w-8 h-8 animate-spin text-orange-500" />
<span className="ml-3 text-gray-600"> ...</span> <span className="ml-3 text-muted-foreground"> ...</span>
</div> </div>
) : error ? ( ) : error ? (
<div className="flex flex-col items-center justify-center py-16"> <div className="flex flex-col items-center justify-center py-16">
<AlertCircle className="w-12 h-12 text-red-500 mb-4" /> <AlertCircle className="w-12 h-12 text-red-500 mb-4" />
<p className="text-red-600">{error}</p> <p className="text-destructive">{error}</p>
<Button onClick={loadMailDetail} variant="outline" className="mt-4"> <Button onClick={loadMailDetail} variant="outline" className="mt-4">
</Button> </Button>
@ -193,17 +193,17 @@ export default function MailDetailModal({
</div> </div>
<div> <div>
<span className="font-medium text-gray-700">:</span>{" "} <span className="font-medium text-gray-700">:</span>{" "}
<span className="text-gray-600">{mail.to}</span> <span className="text-muted-foreground">{mail.to}</span>
</div> </div>
{mail.cc && ( {mail.cc && (
<div> <div>
<span className="font-medium text-gray-700">:</span>{" "} <span className="font-medium text-gray-700">:</span>{" "}
<span className="text-gray-600">{mail.cc}</span> <span className="text-muted-foreground">{mail.cc}</span>
</div> </div>
)} )}
<div> <div>
<span className="font-medium text-gray-700">:</span>{" "} <span className="font-medium text-gray-700">:</span>{" "}
<span className="text-gray-600"> <span className="text-muted-foreground">
{formatDate(mail.date)} {formatDate(mail.date)}
</span> </span>
</div> </div>
@ -225,7 +225,7 @@ export default function MailDetailModal({
{mail.attachments && mail.attachments.length > 0 && ( {mail.attachments && mail.attachments.length > 0 && (
<div className="bg-gray-50 rounded-lg p-4"> <div className="bg-gray-50 rounded-lg p-4">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<Paperclip className="w-4 h-4 text-gray-600" /> <Paperclip className="w-4 h-4 text-muted-foreground" />
<span className="font-medium text-gray-700"> <span className="font-medium text-gray-700">
({mail.attachments.length}) ({mail.attachments.length})
</span> </span>

View File

@ -30,7 +30,7 @@ export default function MailTemplateCard({
const getCategoryColor = (category?: string) => { const getCategoryColor = (category?: string) => {
const colors: Record<string, string> = { const colors: Record<string, string> = {
welcome: 'bg-blue-100 text-blue-700 border-blue-300', welcome: 'bg-primary/20 text-blue-700 border-blue-300',
promotion: 'bg-purple-100 text-purple-700 border-purple-300', promotion: 'bg-purple-100 text-purple-700 border-purple-300',
notification: 'bg-green-100 text-green-700 border-green-300', notification: 'bg-green-100 text-green-700 border-green-300',
newsletter: 'bg-orange-100 text-orange-700 border-orange-300', newsletter: 'bg-orange-100 text-orange-700 border-orange-300',
@ -52,7 +52,7 @@ export default function MailTemplateCard({
<h3 className="font-semibold text-gray-900 truncate"> <h3 className="font-semibold text-gray-900 truncate">
{template.name} {template.name}
</h3> </h3>
<p className="text-sm text-gray-600 truncate mt-1"> <p className="text-sm text-muted-foreground truncate mt-1">
{template.subject} {template.subject}
</p> </p>
</div> </div>
@ -75,7 +75,7 @@ export default function MailTemplateCard({
<p className="text-xs text-gray-500 mb-2"> {template.components.length}</p> <p className="text-xs text-gray-500 mb-2"> {template.components.length}</p>
<div className="space-y-1"> <div className="space-y-1">
{template.components.slice(0, 3).map((component, idx) => ( {template.components.slice(0, 3).map((component, idx) => (
<div key={idx} className="flex items-center gap-2 text-xs text-gray-600"> <div key={idx} className="flex items-center gap-2 text-xs text-muted-foreground">
<div className="w-1.5 h-1.5 rounded-full bg-orange-400" /> <div className="w-1.5 h-1.5 rounded-full bg-orange-400" />
<span className="capitalize">{component.type}</span> <span className="capitalize">{component.type}</span>
{component.type === 'text' && component.content && ( {component.type === 'text' && component.content && (
@ -110,7 +110,7 @@ export default function MailTemplateCard({
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
className="flex-1 hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300" className="flex-1 hover:bg-accent hover:text-primary hover:border-blue-300"
onClick={() => onPreview(template)} onClick={() => onPreview(template)}
> >
<Eye className="w-4 h-4 mr-1" /> <Eye className="w-4 h-4 mr-1" />
@ -138,7 +138,7 @@ export default function MailTemplateCard({
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
className="hover:bg-red-50 hover:text-red-600 hover:border-red-300" className="hover:bg-destructive/10 hover:text-destructive hover:border-red-300"
onClick={() => onDelete(template)} onClick={() => onDelete(template)}
> >
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />

View File

@ -106,7 +106,7 @@ export default function MailTemplatePreviewModal({
</div> </div>
))} ))}
</div> </div>
<div className="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg"> <div className="mt-6 p-4 bg-accent border border-primary/20 rounded-lg">
<p className="text-xs text-blue-800"> <p className="text-xs text-blue-800">
💡 . 💡 .
</p> </p>
@ -122,15 +122,15 @@ export default function MailTemplatePreviewModal({
<div className="bg-gray-100 px-6 py-4 border-b border-gray-200"> <div className="bg-gray-100 px-6 py-4 border-b border-gray-200">
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
<div className="flex"> <div className="flex">
<span className="font-semibold text-gray-600 w-20">:</span> <span className="font-semibold text-muted-foreground w-20">:</span>
<span className="text-gray-900">{template.subject}</span> <span className="text-gray-900">{template.subject}</span>
</div> </div>
<div className="flex"> <div className="flex">
<span className="font-semibold text-gray-600 w-20">:</span> <span className="font-semibold text-muted-foreground w-20">:</span>
<span className="text-gray-700">your-email@company.com</span> <span className="text-gray-700">your-email@company.com</span>
</div> </div>
<div className="flex"> <div className="flex">
<span className="font-semibold text-gray-600 w-20">:</span> <span className="font-semibold text-muted-foreground w-20">:</span>
<span className="text-gray-700">recipient@example.com</span> <span className="text-gray-700">recipient@example.com</span>
</div> </div>
</div> </div>

View File

@ -117,7 +117,7 @@ export default function CopyScreenModal({ isOpen, onClose, sourceScreen, onCopyS
{/* 원본 화면 정보 */} {/* 원본 화면 정보 */}
<div className="rounded-md bg-gray-50 p-3"> <div className="rounded-md bg-gray-50 p-3">
<h4 className="mb-2 text-sm font-medium text-gray-700"> </h4> <h4 className="mb-2 text-sm font-medium text-gray-700"> </h4>
<div className="space-y-1 text-sm text-gray-600"> <div className="space-y-1 text-sm text-muted-foreground">
<div> <div>
<span className="font-medium">:</span> {sourceScreen?.screenName} <span className="font-medium">:</span> {sourceScreen?.screenName}
</div> </div>

View File

@ -148,7 +148,7 @@ export default function CreateScreenModal({ open, onOpenChange, onCreated }: Cre
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={submitting}> <Button variant="outline" onClick={() => onOpenChange(false)} disabled={submitting}>
</Button> </Button>
<Button onClick={handleSubmit} disabled={!isValid || submitting} className="bg-blue-600 hover:bg-blue-700"> <Button onClick={handleSubmit} disabled={!isValid || submitting} variant="default">
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@ -55,7 +55,7 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
onToggleZoneBorders, onToggleZoneBorders,
}) => { }) => {
return ( return (
<div className="flex items-center justify-between border-b border-gray-200 bg-white px-4 py-3 shadow-sm"> <div className="flex items-center justify-between border-b border-gray-200 bg-gradient-to-r from-gray-50 to-white px-4 py-3 shadow-sm">
{/* 좌측: 네비게이션 및 화면 정보 */} {/* 좌측: 네비게이션 및 화면 정보 */}
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<Button variant="ghost" size="sm" onClick={onBack} className="flex items-center space-x-2"> <Button variant="ghost" size="sm" onClick={onBack} className="flex items-center space-x-2">
@ -66,7 +66,7 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
<div className="h-6 w-px bg-gray-300" /> <div className="h-6 w-px bg-gray-300" />
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<Menu className="h-5 w-5 text-gray-600" /> <Menu className="h-5 w-5 text-muted-foreground" />
<div> <div>
<h1 className="text-lg font-semibold text-gray-900">{screenName || "화면 설계"}</h1> <h1 className="text-lg font-semibold text-gray-900">{screenName || "화면 설계"}</h1>
{tableName && ( {tableName && (
@ -85,7 +85,7 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
variant={panelStates.tables?.isOpen ? "default" : "outline"} variant={panelStates.tables?.isOpen ? "default" : "outline"}
size="sm" size="sm"
onClick={() => onTogglePanel("tables")} onClick={() => onTogglePanel("tables")}
className={cn("flex items-center space-x-2", panelStates.tables?.isOpen && "bg-blue-600 text-white")} className="flex items-center space-x-2"
> >
<Database className="h-4 w-4" /> <Database className="h-4 w-4" />
<span></span> <span></span>
@ -98,7 +98,7 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
variant={panelStates.templates?.isOpen ? "default" : "outline"} variant={panelStates.templates?.isOpen ? "default" : "outline"}
size="sm" size="sm"
onClick={() => onTogglePanel("templates")} onClick={() => onTogglePanel("templates")}
className={cn("flex items-center space-x-2", panelStates.templates?.isOpen && "bg-blue-600 text-white")} className="flex items-center space-x-2"
> >
<Layout className="h-4 w-4" /> <Layout className="h-4 w-4" />
<span>릿</span> <span>릿</span>
@ -111,7 +111,7 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
variant={panelStates.components?.isOpen ? "default" : "outline"} variant={panelStates.components?.isOpen ? "default" : "outline"}
size="sm" size="sm"
onClick={() => onTogglePanel("components")} onClick={() => onTogglePanel("components")}
className={cn("flex items-center space-x-2", panelStates.components?.isOpen && "bg-blue-600 text-white")} className="flex items-center space-x-2"
> >
<Cog className="h-4 w-4" /> <Cog className="h-4 w-4" />
<span></span> <span></span>
@ -124,7 +124,7 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
variant={panelStates.properties?.isOpen ? "default" : "outline"} variant={panelStates.properties?.isOpen ? "default" : "outline"}
size="sm" size="sm"
onClick={() => onTogglePanel("properties")} onClick={() => onTogglePanel("properties")}
className={cn("flex items-center space-x-2", panelStates.properties?.isOpen && "bg-blue-600 text-white")} className="flex items-center space-x-2"
> >
<Settings className="h-4 w-4" /> <Settings className="h-4 w-4" />
<span></span> <span></span>
@ -137,7 +137,7 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
variant={panelStates.styles?.isOpen ? "default" : "outline"} variant={panelStates.styles?.isOpen ? "default" : "outline"}
size="sm" size="sm"
onClick={() => onTogglePanel("styles")} onClick={() => onTogglePanel("styles")}
className={cn("flex items-center space-x-2", panelStates.styles?.isOpen && "bg-blue-600 text-white")} className="flex items-center space-x-2"
> >
<Palette className="h-4 w-4" /> <Palette className="h-4 w-4" />
<span></span> <span></span>

Some files were not shown because too many files have changed in this diff Show More