diff --git a/backend-node/src/controllers/mailAccountFileController.ts b/backend-node/src/controllers/mailAccountFileController.ts
index 702a5dd4..668dedf3 100644
--- a/backend-node/src/controllers/mailAccountFileController.ts
+++ b/backend-node/src/controllers/mailAccountFileController.ts
@@ -178,14 +178,19 @@ export class MailAccountFileController {
try {
const { id } = req.params;
- // TODO: 실제 SMTP 연결 테스트 구현
- // const account = await mailAccountFileService.getAccountById(id);
- // nodemailer로 연결 테스트
+ const account = await mailAccountFileService.getAccountById(id);
+ if (!account) {
+ return res.status(404).json({
+ success: false,
+ message: '계정을 찾을 수 없습니다.',
+ });
+ }
- return res.json({
- success: true,
- message: '연결 테스트 성공 (미구현)',
- });
+ // mailSendSimpleService의 testConnection 사용
+ const { mailSendSimpleService } = require('../services/mailSendSimpleService');
+ const result = await mailSendSimpleService.testConnection(id);
+
+ return res.json(result);
} catch (error: unknown) {
const err = error as Error;
return res.status(500).json({
diff --git a/backend-node/src/controllers/mailSendSimpleController.ts b/backend-node/src/controllers/mailSendSimpleController.ts
index cbd9b647..15a1bea0 100644
--- a/backend-node/src/controllers/mailSendSimpleController.ts
+++ b/backend-node/src/controllers/mailSendSimpleController.ts
@@ -7,10 +7,12 @@ export class MailSendSimpleController {
*/
async sendMail(req: Request, res: Response) {
try {
+ console.log('📧 메일 발송 요청 수신:', { accountId: req.body.accountId, to: req.body.to, subject: req.body.subject });
const { accountId, templateId, to, subject, variables, customHtml } = req.body;
// 필수 파라미터 검증
if (!accountId || !to || !Array.isArray(to) || to.length === 0) {
+ console.log('❌ 필수 파라미터 누락');
return res.status(400).json({
success: false,
message: '계정 ID와 수신자 이메일이 필요합니다.',
diff --git a/backend-node/src/routes/mailAccountFileRoutes.ts b/backend-node/src/routes/mailAccountFileRoutes.ts
index cc022cb8..35772d39 100644
--- a/backend-node/src/routes/mailAccountFileRoutes.ts
+++ b/backend-node/src/routes/mailAccountFileRoutes.ts
@@ -1,8 +1,12 @@
import { Router } from 'express';
import { mailAccountFileController } from '../controllers/mailAccountFileController';
+import { authenticateToken } from '../middleware/authMiddleware';
const router = Router();
+// 모든 메일 계정 라우트에 인증 미들웨어 적용
+router.use(authenticateToken);
+
router.get('/', (req, res) => mailAccountFileController.getAllAccounts(req, res));
router.get('/:id', (req, res) => mailAccountFileController.getAccountById(req, res));
router.post('/', (req, res) => mailAccountFileController.createAccount(req, res));
diff --git a/backend-node/src/routes/mailReceiveBasicRoutes.ts b/backend-node/src/routes/mailReceiveBasicRoutes.ts
index f8d0d670..d21df689 100644
--- a/backend-node/src/routes/mailReceiveBasicRoutes.ts
+++ b/backend-node/src/routes/mailReceiveBasicRoutes.ts
@@ -4,8 +4,12 @@
import express from 'express';
import { MailReceiveBasicController } from '../controllers/mailReceiveBasicController';
+import { authenticateToken } from '../middleware/authMiddleware';
const router = express.Router();
+
+// 모든 메일 수신 라우트에 인증 미들웨어 적용
+router.use(authenticateToken);
const controller = new MailReceiveBasicController();
// 메일 목록 조회
diff --git a/backend-node/src/routes/mailSendSimpleRoutes.ts b/backend-node/src/routes/mailSendSimpleRoutes.ts
index db56b66d..726a220c 100644
--- a/backend-node/src/routes/mailSendSimpleRoutes.ts
+++ b/backend-node/src/routes/mailSendSimpleRoutes.ts
@@ -1,8 +1,12 @@
import { Router } from 'express';
import { mailSendSimpleController } from '../controllers/mailSendSimpleController';
+import { authenticateToken } from '../middleware/authMiddleware';
const router = Router();
+// 모든 메일 발송 라우트에 인증 미들웨어 적용
+router.use(authenticateToken);
+
// POST /api/mail/send/simple - 메일 발송
router.post('/simple', (req, res) => mailSendSimpleController.sendMail(req, res));
diff --git a/backend-node/src/routes/mailTemplateFileRoutes.ts b/backend-node/src/routes/mailTemplateFileRoutes.ts
index eb79ed34..a4f81b1b 100644
--- a/backend-node/src/routes/mailTemplateFileRoutes.ts
+++ b/backend-node/src/routes/mailTemplateFileRoutes.ts
@@ -1,8 +1,12 @@
import { Router } from 'express';
import { mailTemplateFileController } from '../controllers/mailTemplateFileController';
+import { authenticateToken } from '../middleware/authMiddleware';
const router = Router();
+// 모든 메일 템플릿 라우트에 인증 미들웨어 적용
+router.use(authenticateToken);
+
// 템플릿 CRUD
router.get('/', (req, res) => mailTemplateFileController.getAllTemplates(req, res));
router.get('/:id', (req, res) => mailTemplateFileController.getTemplateById(req, res));
diff --git a/backend-node/src/services/mailSendSimpleService.ts b/backend-node/src/services/mailSendSimpleService.ts
index c5d2fc4f..473f3959 100644
--- a/backend-node/src/services/mailSendSimpleService.ts
+++ b/backend-node/src/services/mailSendSimpleService.ts
@@ -6,6 +6,7 @@
import nodemailer from 'nodemailer';
import { mailAccountFileService } from './mailAccountFileService';
import { mailTemplateFileService } from './mailTemplateFileService';
+import { encryptionService } from './encryptionService';
export interface SendMailRequest {
accountId: string;
@@ -56,18 +57,39 @@ class MailSendSimpleService {
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({
host: account.smtpHost,
port: account.smtpPort,
- secure: account.smtpSecure, // SSL/TLS
+ secure: isSecure, // SSL/TLS (포트 465는 자동으로 true)
auth: {
user: account.smtpUsername,
- pass: account.smtpPassword,
+ pass: decryptedPassword, // 복호화된 비밀번호 사용
},
+ // 타임아웃 설정 (30초)
+ connectionTimeout: 30000,
+ greetingTimeout: 30000,
});
- // 5. 메일 발송
+ console.log('📧 메일 발송 시도 중...');
+
+ // 6. 메일 발송
const info = await transporter.sendMail({
from: `"${account.name}" <${account.email}>`,
to: request.to.join(', '),
@@ -75,6 +97,12 @@ class MailSendSimpleService {
html: htmlContent,
});
+ console.log('✅ 메일 발송 성공:', {
+ messageId: info.messageId,
+ accepted: info.accepted,
+ rejected: info.rejected,
+ });
+
return {
success: true,
messageId: info.messageId,
@@ -83,6 +111,8 @@ class MailSendSimpleService {
};
} catch (error) {
const err = error as Error;
+ console.error('❌ 메일 발송 실패:', err.message);
+ console.error('❌ 에러 상세:', err);
return {
success: false,
error: err.message,
@@ -178,22 +208,42 @@ class MailSendSimpleService {
*/
async testConnection(accountId: string): Promise<{ success: boolean; message: string }> {
try {
+ console.log('🔌 SMTP 연결 테스트 시작:', accountId);
+
const account = await mailAccountFileService.getAccountById(accountId);
if (!account) {
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({
host: account.smtpHost,
port: account.smtpPort,
- secure: account.smtpSecure,
+ secure: isSecure,
auth: {
user: account.smtpUsername,
- pass: account.smtpPassword,
+ pass: decryptedPassword, // 복호화된 비밀번호 사용
},
+ connectionTimeout: 10000, // 10초 타임아웃
+ greetingTimeout: 10000,
});
+ console.log('🔌 SMTP 연결 검증 중...');
await transporter.verify();
+ console.log('✅ SMTP 연결 검증 성공!');
return {
success: true,
@@ -201,6 +251,7 @@ class MailSendSimpleService {
};
} catch (error) {
const err = error as Error;
+ console.error('❌ SMTP 연결 실패:', err.message);
return {
success: false,
message: `연결 실패: ${err.message}`,
diff --git a/frontend/app/(main)/admin/mail/accounts/page.tsx b/frontend/app/(main)/admin/mail/accounts/page.tsx
index 0171f2b6..ca0cf0b9 100644
--- a/frontend/app/(main)/admin/mail/accounts/page.tsx
+++ b/frontend/app/(main)/admin/mail/accounts/page.tsx
@@ -10,6 +10,7 @@ import {
createMailAccount,
updateMailAccount,
deleteMailAccount,
+ testMailAccountConnection,
CreateMailAccountDto,
UpdateMailAccountDto,
} 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 (
@@ -148,6 +167,7 @@ export default function MailAccountsPage() {
onEdit={handleOpenEditModal}
onDelete={handleOpenDeleteModal}
onToggleStatus={handleToggleStatus}
+ onTestConnection={handleTestConnection}
/>
diff --git a/frontend/app/(main)/admin/mail/dashboard/page.tsx b/frontend/app/(main)/admin/mail/dashboard/page.tsx
index 1fa6a728..f2e737bc 100644
--- a/frontend/app/(main)/admin/mail/dashboard/page.tsx
+++ b/frontend/app/(main)/admin/mail/dashboard/page.tsx
@@ -14,6 +14,7 @@ import {
Calendar,
Clock
} from "lucide-react";
+import { getMailAccounts, getMailTemplates } from "@/lib/api/mail";
interface DashboardStats {
totalAccounts: number;
@@ -38,17 +39,15 @@ export default function MailDashboardPage() {
const loadStats = async () => {
setLoading(true);
try {
- // 계정 수
- const accountsRes = await fetch('/api/mail/accounts');
- const accountsData = await accountsRes.json();
+ // 계정 수 (apiClient를 통해 토큰 포함)
+ const accounts = await getMailAccounts();
- // 템플릿 수
- const templatesRes = await fetch('/api/mail/templates-file');
- const templatesData = await templatesRes.json();
+ // 템플릿 수 (apiClient를 통해 토큰 포함)
+ const templates = await getMailTemplates();
setStats({
- totalAccounts: accountsData.success ? accountsData.data.length : 0,
- totalTemplates: templatesData.success ? templatesData.data.length : 0,
+ totalAccounts: accounts.length,
+ totalTemplates: templates.length,
sentToday: 0, // TODO: 실제 발송 통계 API 연동
receivedToday: 0,
sentThisMonth: 0,
diff --git a/frontend/app/(main)/admin/screenMng/page.tsx b/frontend/app/(main)/admin/screenMng/page.tsx
index 88dff27f..54da701b 100644
--- a/frontend/app/(main)/admin/screenMng/page.tsx
+++ b/frontend/app/(main)/admin/screenMng/page.tsx
@@ -83,7 +83,7 @@ export default function ScreenManagementPage() {
{stepConfig.list.title}
-
@@ -121,7 +121,7 @@ export default function ScreenManagementPage() {
이전 단계
-
goToStep("list")}>
+ goToStep("list")}>
목록으로 돌아가기
diff --git a/frontend/app/(main)/admin/validation-demo/page.tsx b/frontend/app/(main)/admin/validation-demo/page.tsx
index f84a06f5..2372c4ea 100644
--- a/frontend/app/(main)/admin/validation-demo/page.tsx
+++ b/frontend/app/(main)/admin/validation-demo/page.tsx
@@ -54,7 +54,7 @@ const TEST_COMPONENTS: ComponentData[] = [
required: true,
style: {
labelFontSize: "14px",
- labelColor: "#3b83f6",
+ labelColor: "#212121",
labelFontWeight: "500",
},
} as WidgetComponent,
@@ -72,7 +72,7 @@ const TEST_COMPONENTS: ComponentData[] = [
required: true,
style: {
labelFontSize: "14px",
- labelColor: "#3b83f6",
+ labelColor: "#212121",
labelFontWeight: "500",
},
} as WidgetComponent,
@@ -94,7 +94,7 @@ const TEST_COMPONENTS: ComponentData[] = [
},
style: {
labelFontSize: "14px",
- labelColor: "#3b83f6",
+ labelColor: "#212121",
labelFontWeight: "500",
},
} as WidgetComponent,
@@ -112,7 +112,7 @@ const TEST_COMPONENTS: ComponentData[] = [
required: false,
style: {
labelFontSize: "14px",
- labelColor: "#3b83f6",
+ labelColor: "#212121",
labelFontWeight: "500",
},
} as WidgetComponent,
@@ -130,7 +130,7 @@ const TEST_COMPONENTS: ComponentData[] = [
required: false,
style: {
labelFontSize: "14px",
- labelColor: "#3b83f6",
+ labelColor: "#212121",
labelFontWeight: "500",
},
} as WidgetComponent,
@@ -152,7 +152,7 @@ const TEST_COMPONENTS: ComponentData[] = [
},
style: {
labelFontSize: "14px",
- labelColor: "#3b83f6",
+ labelColor: "#212121",
labelFontWeight: "500",
},
} as WidgetComponent,
diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx
index 93111ba8..8b186bfa 100644
--- a/frontend/app/(main)/screens/[screenId]/page.tsx
+++ b/frontend/app/(main)/screens/[screenId]/page.tsx
@@ -148,7 +148,7 @@ export default function ScreenViewPage() {
const screenHeight = layout?.screenResolution?.height || 800;
return (
-
+
{layout && layout.components.length > 0 ? (
// 캔버스 컴포넌트들을 정확한 해상도로 표시
{layout.components
@@ -239,7 +238,7 @@ export default function ScreenViewPage() {
const labelText = component.style?.labelText || component.label || "";
const labelStyle = {
fontSize: component.style?.labelFontSize || "14px",
- color: component.style?.labelColor || "#3b83f6",
+ color: component.style?.labelColor || "#212121",
fontWeight: component.style?.labelFontWeight || "500",
backgroundColor: component.style?.labelBackgroundColor || "transparent",
padding: component.style?.labelPadding || "0",
@@ -379,7 +378,7 @@ export default function ScreenViewPage() {
) : (
// 빈 화면일 때도 깔끔하게 표시
= ({
// 파일 아이콘 가져오기
const getFileIcon = (fileName: string, size: number = 16) => {
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)) {
- return
;
+ return
;
}
if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm'].includes(extension)) {
return
;
@@ -71,7 +71,7 @@ export const GlobalFileViewer: React.FC
= ({
return ;
}
if (['txt', 'md', 'doc', 'docx', 'pdf', 'rtf'].includes(extension)) {
- return ;
+ return ;
}
return ;
};
@@ -272,7 +272,7 @@ export const GlobalFileViewer: React.FC = ({
variant="ghost"
size="sm"
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"
>
diff --git a/frontend/components/admin/BatchJobModal.tsx b/frontend/components/admin/BatchJobModal.tsx
index 21eb8df9..e7f75f77 100644
--- a/frontend/components/admin/BatchJobModal.tsx
+++ b/frontend/components/admin/BatchJobModal.tsx
@@ -179,7 +179,7 @@ export default function BatchJobModal({
const getStatusColor = (status: string) => {
switch (status) {
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';
}
};
@@ -314,29 +314,29 @@ export default function BatchJobModal({
-
+
{formData.execution_count || 0}
-
총 실행 횟수
+
총 실행 횟수
{formData.success_count || 0}
-
성공 횟수
+
성공 횟수
-
+
{formData.failure_count || 0}
-
실패 횟수
+
실패 횟수
{formData.last_executed_at && (
-
+
마지막 실행: {new Date(formData.last_executed_at).toLocaleString()}
)}
diff --git a/frontend/components/admin/CategoryItem.tsx b/frontend/components/admin/CategoryItem.tsx
index 92b074b0..b5b34c17 100644
--- a/frontend/components/admin/CategoryItem.tsx
+++ b/frontend/components/admin/CategoryItem.tsx
@@ -55,7 +55,7 @@ export function CategoryItem({ category, isSelected, onSelect, onEdit, onDelete
"cursor-pointer transition-colors",
category.is_active === "Y"
? "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",
)}
onClick={(e) => {
@@ -71,7 +71,7 @@ export function CategoryItem({ category, isSelected, onSelect, onEdit, onDelete
{category.is_active === "Y" ? "활성" : "비활성"}
-
{category.category_code}
+
{category.category_code}
{category.description &&
{category.description}
}
diff --git a/frontend/components/admin/CodeCategoryFormModal.tsx b/frontend/components/admin/CodeCategoryFormModal.tsx
index 8a6fbd34..67fe8993 100644
--- a/frontend/components/admin/CodeCategoryFormModal.tsx
+++ b/frontend/components/admin/CodeCategoryFormModal.tsx
@@ -180,11 +180,11 @@ export function CodeCategoryFormModal({
{...createForm.register("categoryCode")}
disabled={isLoading}
placeholder="카테고리 코드를 입력하세요"
- className={createForm.formState.errors.categoryCode ? "border-red-500" : ""}
+ className={createForm.formState.errors.categoryCode ? "border-destructive" : ""}
onBlur={() => handleFieldBlur("categoryCode")}
/>
{createForm.formState.errors.categoryCode && (
-
{createForm.formState.errors.categoryCode.message}
+
{createForm.formState.errors.categoryCode.message}
)}
{!createForm.formState.errors.categoryCode && (
-
+
카테고리 코드는 수정할 수 없습니다.
)}
@@ -216,20 +216,20 @@ export function CodeCategoryFormModal({
className={
isEditing
? updateForm.formState.errors.categoryName
- ? "border-red-500"
+ ? "border-destructive"
: ""
: createForm.formState.errors.categoryName
- ? "border-red-500"
+ ? "border-destructive"
: ""
}
onBlur={() => handleFieldBlur("categoryName")}
/>
{isEditing
? updateForm.formState.errors.categoryName && (
- {updateForm.formState.errors.categoryName.message}
+ {updateForm.formState.errors.categoryName.message}
)
: createForm.formState.errors.categoryName && (
- {createForm.formState.errors.categoryName.message}
+ {createForm.formState.errors.categoryName.message}
)}
{!(isEditing ? updateForm.formState.errors.categoryName : createForm.formState.errors.categoryName) && (
handleFieldBlur("categoryNameEng")}
/>
{isEditing
? updateForm.formState.errors.categoryNameEng && (
- {updateForm.formState.errors.categoryNameEng.message}
+ {updateForm.formState.errors.categoryNameEng.message}
)
: createForm.formState.errors.categoryNameEng && (
- {createForm.formState.errors.categoryNameEng.message}
+ {createForm.formState.errors.categoryNameEng.message}
)}
{!(isEditing
? updateForm.formState.errors.categoryNameEng
@@ -289,20 +289,20 @@ export function CodeCategoryFormModal({
className={
isEditing
? updateForm.formState.errors.description
- ? "border-red-500"
+ ? "border-destructive"
: ""
: createForm.formState.errors.description
- ? "border-red-500"
+ ? "border-destructive"
: ""
}
onBlur={() => handleFieldBlur("description")}
/>
{isEditing
? updateForm.formState.errors.description && (
- {updateForm.formState.errors.description.message}
+ {updateForm.formState.errors.description.message}
)
: createForm.formState.errors.description && (
- {createForm.formState.errors.description.message}
+ {createForm.formState.errors.description.message}
)}
@@ -320,19 +320,19 @@ export function CodeCategoryFormModal({
className={
isEditing
? updateForm.formState.errors.sortOrder
- ? "border-red-500"
+ ? "border-destructive"
: ""
: createForm.formState.errors.sortOrder
- ? "border-red-500"
+ ? "border-destructive"
: ""
}
/>
{isEditing
? updateForm.formState.errors.sortOrder && (
-
{updateForm.formState.errors.sortOrder.message}
+
{updateForm.formState.errors.sortOrder.message}
)
: createForm.formState.errors.sortOrder && (
-
{createForm.formState.errors.sortOrder.message}
+
{createForm.formState.errors.sortOrder.message}
)}
diff --git a/frontend/components/admin/CodeCategoryPanel.tsx b/frontend/components/admin/CodeCategoryPanel.tsx
index 96f75d04..645193fc 100644
--- a/frontend/components/admin/CodeCategoryPanel.tsx
+++ b/frontend/components/admin/CodeCategoryPanel.tsx
@@ -82,7 +82,7 @@ export function CodeCategoryPanel({ selectedCategoryCode, onSelectCategory }: Co
return (
-
카테고리를 불러오는 중 오류가 발생했습니다.
+
카테고리를 불러오는 중 오류가 발생했습니다.
window.location.reload()} className="mt-2">
다시 시도
@@ -116,7 +116,7 @@ export function CodeCategoryPanel({ selectedCategoryCode, onSelectCategory }: Co
onChange={(e) => setShowActiveOnly(e.target.checked)}
className="rounded border-gray-300"
/>
-
diff --git a/frontend/components/admin/CodeDetailPanel.tsx b/frontend/components/admin/CodeDetailPanel.tsx
index 680f59f4..3389ad5b 100644
--- a/frontend/components/admin/CodeDetailPanel.tsx
+++ b/frontend/components/admin/CodeDetailPanel.tsx
@@ -121,7 +121,7 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
return (
-
코드를 불러오는 중 오류가 발생했습니다.
+
코드를 불러오는 중 오류가 발생했습니다.
window.location.reload()} className="mt-2">
다시 시도
@@ -155,7 +155,7 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
onChange={(e) => setShowActiveOnly(e.target.checked)}
className="rounded border-gray-300"
/>
-
+
활성 코드만 표시
@@ -221,13 +221,13 @@ export function CodeDetailPanel({ categoryCode }: CodeDetailPanelProps) {
"transition-colors",
activeCode.isActive === "Y" || activeCode.is_active === "Y"
? "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.codeValue || activeCode.code_value}
{activeCode.description && (
diff --git a/frontend/components/admin/CodeFormModal.tsx b/frontend/components/admin/CodeFormModal.tsx
index 6e915904..26b617a4 100644
--- a/frontend/components/admin/CodeFormModal.tsx
+++ b/frontend/components/admin/CodeFormModal.tsx
@@ -168,7 +168,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
{...form.register("codeValue")}
disabled={isLoading || isEditing} // 수정 시에는 비활성화
placeholder="코드값을 입력하세요"
- className={(form.formState.errors as any)?.codeValue ? "border-red-500" : ""}
+ className={(form.formState.errors as any)?.codeValue ? "border-destructive" : ""}
onBlur={(e) => {
const value = e.target.value.trim();
if (value && !isEditing) {
@@ -180,7 +180,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
}}
/>
{(form.formState.errors as any)?.codeValue && (
-
{getErrorMessage((form.formState.errors as any)?.codeValue)}
+
{getErrorMessage((form.formState.errors as any)?.codeValue)}
)}
{!isEditing && !(form.formState.errors as any)?.codeValue && (
{
const value = e.target.value.trim();
if (value) {
@@ -211,7 +211,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
}}
/>
{form.formState.errors.codeName && (
- {getErrorMessage(form.formState.errors.codeName)}
+ {getErrorMessage(form.formState.errors.codeName)}
)}
{!form.formState.errors.codeName && (
{
const value = e.target.value.trim();
if (value) {
@@ -242,7 +242,7 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
}}
/>
{form.formState.errors.codeNameEng && (
- {getErrorMessage(form.formState.errors.codeNameEng)}
+ {getErrorMessage(form.formState.errors.codeNameEng)}
)}
{!form.formState.errors.codeNameEng && (
{form.formState.errors.description && (
- {getErrorMessage(form.formState.errors.description)}
+ {getErrorMessage(form.formState.errors.description)}
)}
@@ -278,10 +278,10 @@ export function CodeFormModal({ isOpen, onClose, categoryCode, editingCode, code
{...form.register("sortOrder", { valueAsNumber: true })}
disabled={isLoading}
min={1}
- className={form.formState.errors.sortOrder ? "border-red-500" : ""}
+ className={form.formState.errors.sortOrder ? "border-destructive" : ""}
/>
{form.formState.errors.sortOrder && (
-
{getErrorMessage(form.formState.errors.sortOrder)}
+
{getErrorMessage(form.formState.errors.sortOrder)}
)}
diff --git a/frontend/components/admin/ColumnDefinitionTable.tsx b/frontend/components/admin/ColumnDefinitionTable.tsx
index c58ed39b..74fd4f33 100644
--- a/frontend/components/admin/ColumnDefinitionTable.tsx
+++ b/frontend/components/admin/ColumnDefinitionTable.tsx
@@ -188,7 +188,7 @@ export function ColumnDefinitionTable({ columns, onChange, disabled = false }: C
const hasRowError = rowErrors.length > 0;
return (
-
+
{rowErrors.length > 0 && (
-
+
{rowErrors.map((error, i) => (
{error}
))}
diff --git a/frontend/components/admin/CreateTableModal.tsx b/frontend/components/admin/CreateTableModal.tsx
index 07dae653..7e075ad1 100644
--- a/frontend/components/admin/CreateTableModal.tsx
+++ b/frontend/components/admin/CreateTableModal.tsx
@@ -248,7 +248,7 @@ export function CreateTableModal({ isOpen, onClose, onSuccess }: CreateTableModa
placeholder="예: customer_info"
className={tableNameError ? "border-red-300" : ""}
/>
- {tableNameError &&
{tableNameError}
}
+ {tableNameError &&
{tableNameError}
}
영문자로 시작, 영문자/숫자/언더스코어만 사용 가능
diff --git a/frontend/components/admin/DDLLogViewer.tsx b/frontend/components/admin/DDLLogViewer.tsx
index c978f162..e0184f38 100644
--- a/frontend/components/admin/DDLLogViewer.tsx
+++ b/frontend/components/admin/DDLLogViewer.tsx
@@ -271,14 +271,14 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
{log.success ? (
) : (
-
+
)}
-
+
{log.success ? "성공" : "실패"}
{log.error_message && (
-
{log.error_message}
+
{log.error_message}
)}
@@ -325,7 +325,7 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
실패
- {statistics.failedExecutions}
+ {statistics.failedExecutions}
@@ -374,13 +374,13 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
{statistics.recentFailures.length > 0 && (
- 최근 실패 로그
+ 최근 실패 로그
최근 발생한 DDL 실행 실패 내역입니다.
{statistics.recentFailures.map((failure, index) => (
-
+
{failure.ddl_type}
@@ -390,7 +390,7 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
{format(new Date(failure.executed_at), "MM-dd HH:mm", { locale: ko })}
-
{failure.error_message}
+
{failure.error_message}
))}
diff --git a/frontend/components/admin/DiskUsageSummary.tsx b/frontend/components/admin/DiskUsageSummary.tsx
index 59af8455..096af5f8 100644
--- a/frontend/components/admin/DiskUsageSummary.tsx
+++ b/frontend/components/admin/DiskUsageSummary.tsx
@@ -120,7 +120,7 @@ export function DiskUsageSummary({ diskUsageInfo, isLoading, onRefresh }: DiskUs
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={{
width: `${Math.min((summary.totalSizeMB / 2000) * 100, 100)}%`,
diff --git a/frontend/components/admin/ExternalDbConnectionModal.tsx b/frontend/components/admin/ExternalDbConnectionModal.tsx
index cb23768f..b9075bc8 100644
--- a/frontend/components/admin/ExternalDbConnectionModal.tsx
+++ b/frontend/components/admin/ExternalDbConnectionModal.tsx
@@ -450,7 +450,7 @@ export const ExternalDbConnectionModal: React.FC
className={`rounded-md border p-3 text-sm ${
testResult.success
? "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"
}`}
>
{testResult.success ? "✅ 연결 성공" : "❌ 연결 실패"}
@@ -469,7 +469,7 @@ export const ExternalDbConnectionModal: React.FC
{!testResult.success && testResult.error && (
오류 코드: {testResult.error.code}
- {testResult.error.details &&
{testResult.error.details}
}
+ {testResult.error.details &&
{testResult.error.details}
}
)}
diff --git a/frontend/components/admin/LayoutFormModal.tsx b/frontend/components/admin/LayoutFormModal.tsx
index 972caa7c..b2fe9804 100644
--- a/frontend/components/admin/LayoutFormModal.tsx
+++ b/frontend/components/admin/LayoutFormModal.tsx
@@ -238,10 +238,10 @@ export const LayoutFormModal: React.FC
= ({ open, onOpenCh
1
@@ -249,19 +249,19 @@ export const LayoutFormModal: React.FC
= ({ open, onOpenCh
-
+
3
@@ -304,13 +304,13 @@ export const LayoutFormModal: React.FC
= ({ open, onOpenCh
setFormData((prev) => ({ ...prev, category: category.id }))}
>
-
+
{category.name}
{category.description}
@@ -346,7 +346,7 @@ export const LayoutFormModal: React.FC
= ({ open, onOpenCh
setFormData((prev) => ({
@@ -362,7 +362,7 @@ export const LayoutFormModal: React.FC = ({ open, onOpenCh
{template.name}
{template.zones}개 영역
-
{template.description}
+
{template.description}
예: {template.example}
{template.icon}
@@ -427,7 +427,7 @@ export const LayoutFormModal: React.FC = ({ open, onOpenCh
{generationResult ? (
@@ -479,7 +479,7 @@ export const LayoutFormModal: React.FC = ({ open, onOpenCh
생성될 파일:
-
+
- • {formData.name.toLowerCase()}/index.ts
-
• {formData.name.toLowerCase()}/{formData.name}Layout.tsx
diff --git a/frontend/components/admin/MenuFormModal.tsx b/frontend/components/admin/MenuFormModal.tsx
index 501a0b8d..f8d80592 100644
--- a/frontend/components/admin/MenuFormModal.tsx
+++ b/frontend/components/admin/MenuFormModal.tsx
@@ -826,10 +826,10 @@ export const MenuFormModal: React.FC = ({
{/* 선택된 화면 정보 표시 */}
{selectedScreen && (
-
+
{selectedScreen.screenName}
-
코드: {selectedScreen.screenCode}
-
생성된 URL: {formData.menuUrl}
+
코드: {selectedScreen.screenCode}
+
생성된 URL: {formData.menuUrl}
)}
diff --git a/frontend/components/admin/MenuManagement.tsx b/frontend/components/admin/MenuManagement.tsx
index 81c94ae0..ab7ec016 100644
--- a/frontend/components/admin/MenuManagement.tsx
+++ b/frontend/components/admin/MenuManagement.tsx
@@ -828,7 +828,7 @@ export const MenuManagement: React.FC = () => {
handleMenuTypeChange("admin")}
>
@@ -836,7 +836,7 @@ export const MenuManagement: React.FC = () => {
{getUITextSync("menu.management.admin")}
-
+
{getUITextSync("menu.management.admin.description")}
@@ -849,7 +849,7 @@ export const MenuManagement: React.FC = () => {
handleMenuTypeChange("user")}
>
@@ -857,7 +857,7 @@ export const MenuManagement: React.FC = () => {
{getUITextSync("menu.management.user")}
-
+
{getUITextSync("menu.management.user.description")}
@@ -997,7 +997,7 @@ export const MenuManagement: React.FC = () => {
-
+
{getUITextSync("menu.list.search.result", { count: getCurrentMenus().length })}
@@ -1006,7 +1006,7 @@ export const MenuManagement: React.FC = () => {
-
+
{getUITextSync("menu.list.total", { count: getCurrentMenus().length })}
diff --git a/frontend/components/admin/MenuTable.tsx b/frontend/components/admin/MenuTable.tsx
index 40b01fa3..5df95d81 100644
--- a/frontend/components/admin/MenuTable.tsx
+++ b/frontend/components/admin/MenuTable.tsx
@@ -67,7 +67,7 @@ export const MenuTable: React.FC = ({
const getLevelBadge = (level: number) => {
switch (level) {
case 0:
- return "bg-blue-100 text-blue-800";
+ return "bg-primary/20 text-blue-800";
case 1:
return "bg-green-100 text-green-800";
case 2:
@@ -239,7 +239,7 @@ export const MenuTable: React.FC = ({
{seq}
-
+
= ({
)}
-
+
{menuUrl ? (
30 ? "truncate" : ""
}`}
onClick={() => {
diff --git a/frontend/components/admin/MonitoringDashboard.tsx b/frontend/components/admin/MonitoringDashboard.tsx
index 43fc1819..500dd4fb 100644
--- a/frontend/components/admin/MonitoringDashboard.tsx
+++ b/frontend/components/admin/MonitoringDashboard.tsx
@@ -74,8 +74,8 @@ export default function MonitoringDashboard() {
const getStatusBadge = (status: string) => {
const variants = {
completed: "bg-green-100 text-green-800",
- failed: "bg-red-100 text-red-800",
- running: "bg-blue-100 text-blue-800",
+ failed: "bg-destructive/20 text-red-800",
+ running: "bg-primary/20 text-blue-800",
pending: "bg-yellow-100 text-yellow-800",
cancelled: "bg-gray-100 text-gray-800",
};
@@ -129,7 +129,7 @@ export default function MonitoringDashboard() {
variant="outline"
size="sm"
onClick={toggleAutoRefresh}
- className={autoRefresh ? "bg-blue-50 text-blue-600" : ""}
+ className={autoRefresh ? "bg-accent text-primary" : ""}
>
{autoRefresh ?
:
}
자동 새로고침
@@ -167,7 +167,7 @@ export default function MonitoringDashboard() {
🔄
- {monitoring.running_jobs}
+ {monitoring.running_jobs}
현재 실행 중인 작업
@@ -193,7 +193,7 @@ export default function MonitoringDashboard() {
❌
- {monitoring.failed_jobs_today}
+ {monitoring.failed_jobs_today}
주의가 필요한 작업
@@ -269,7 +269,7 @@ export default function MonitoringDashboard() {
{execution.error_message ? (
-
+
{execution.error_message}
) : (
diff --git a/frontend/components/admin/MultiLang.tsx b/frontend/components/admin/MultiLang.tsx
index 0d9c2a98..abdadcdb 100644
--- a/frontend/components/admin/MultiLang.tsx
+++ b/frontend/components/admin/MultiLang.tsx
@@ -673,7 +673,7 @@ export default function MultiLangPage() {
setActiveTab("keys")}
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() {
setActiveTab("languages")}
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() {
-
총 {languages.length}개의 언어가 등록되어 있습니다.
+
총 {languages.length}개의 언어가 등록되어 있습니다.
{selectedLanguages.size > 0 && (
@@ -759,13 +759,13 @@ export default function MultiLangPage() {
-
검색 결과: {getFilteredLangKeys().length}건
+
검색 결과: {getFilteredLangKeys().length}건
{/* 테이블 영역 */}
-
전체: {getFilteredLangKeys().length}건
+
전체: {getFilteredLangKeys().length}건
= ({ menus
(selectedMenu as any).menu_name_kor ||
"메뉴"}
-
+
URL: {selectedMenu.menu_url || selectedMenu.MENU_URL || (selectedMenu as any).menu_url || "없음"}
-
+
설명:{" "}
{selectedMenu.menu_desc || selectedMenu.MENU_DESC || (selectedMenu as any).menu_desc || "없음"}
@@ -294,7 +294,7 @@ export const ScreenAssignmentTab: React.FC = ({ menus
{screen.isActive === "Y" ? "활성" : "비활성"}
-
+
테이블: {screen.tableName} | 생성일: {screen.createdDate.toLocaleDateString()}
{screen.description && {screen.description}
}
@@ -306,7 +306,7 @@ export const ScreenAssignmentTab: React.FC = ({ menus
setSelectedScreen(screen);
setShowUnassignDialog(true);
}}
- className="text-red-600 hover:text-red-700"
+ className="text-destructive hover:text-red-700"
>
@@ -347,7 +347,7 @@ export const ScreenAssignmentTab: React.FC = ({ menus
setSelectedScreen(screen)}
>
@@ -357,7 +357,7 @@ export const ScreenAssignmentTab: React.FC = ({ menus
{screen.screenCode}
- 테이블: {screen.tableName}
+ 테이블: {screen.tableName}
))
)}
diff --git a/frontend/components/admin/SortableCodeItem.tsx b/frontend/components/admin/SortableCodeItem.tsx
index c3731be0..21d456c7 100644
--- a/frontend/components/admin/SortableCodeItem.tsx
+++ b/frontend/components/admin/SortableCodeItem.tsx
@@ -83,7 +83,7 @@ export function SortableCodeItem({
"cursor-pointer transition-colors",
code.isActive === "Y" || code.is_active === "Y"
? "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",
)}
onClick={(e) => {
@@ -100,7 +100,7 @@ export function SortableCodeItem({
{code.isActive === "Y" || code.is_active === "Y" ? "활성" : "비활성"}
-
{code.codeValue || code.code_value}
+
{code.codeValue || code.code_value}
{code.description &&
{code.description}
}
diff --git a/frontend/components/admin/UserFormModal.tsx b/frontend/components/admin/UserFormModal.tsx
index 0e32a95c..8427fa00 100644
--- a/frontend/components/admin/UserFormModal.tsx
+++ b/frontend/components/admin/UserFormModal.tsx
@@ -24,9 +24,9 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
case "success":
return "text-green-600";
case "error":
- return "text-red-600";
+ return "text-destructive";
default:
- return "text-blue-600";
+ return "text-primary";
}
};
@@ -37,7 +37,7 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
{title}
@@ -398,7 +398,7 @@ export function UserFormModal({ isOpen, onClose, onSuccess }: UserFormModalProps
{/* 중복확인 결과 메시지 */}
{duplicateCheckMessage && (
{duplicateCheckMessage}
diff --git a/frontend/components/admin/UserPasswordResetModal.tsx b/frontend/components/admin/UserPasswordResetModal.tsx
index 46928bd3..086b1556 100644
--- a/frontend/components/admin/UserPasswordResetModal.tsx
+++ b/frontend/components/admin/UserPasswordResetModal.tsx
@@ -196,7 +196,7 @@ export function UserPasswordResetModal({ isOpen, onClose, userId, userName, onSu
{/* 비밀번호 일치 여부 표시 */}
- {showMismatchError && 비밀번호가 일치하지 않습니다.
}
+ {showMismatchError && 비밀번호가 일치하지 않습니다.
}
{isPasswordMatch && 비밀번호가 일치합니다.
}
diff --git a/frontend/components/admin/UserStatusConfirmDialog.tsx b/frontend/components/admin/UserStatusConfirmDialog.tsx
index 72ab1aa3..59a7c1df 100644
--- a/frontend/components/admin/UserStatusConfirmDialog.tsx
+++ b/frontend/components/admin/UserStatusConfirmDialog.tsx
@@ -33,8 +33,8 @@ export function UserStatusConfirmDialog({
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 currentStatusColor = user.status === "active" ? "text-blue-600" : "text-gray-600";
- const newStatusColor = newStatus === "active" ? "text-blue-600" : "text-gray-600";
+ const currentStatusColor = user.status === "active" ? "text-primary" : "text-muted-foreground";
+ const newStatusColor = newStatus === "active" ? "text-primary" : "text-muted-foreground";
return (
diff --git a/frontend/components/admin/dashboard/ChartConfigPanel.tsx b/frontend/components/admin/dashboard/ChartConfigPanel.tsx
index 5ba617a3..d67cfefb 100644
--- a/frontend/components/admin/dashboard/ChartConfigPanel.tsx
+++ b/frontend/components/admin/dashboard/ChartConfigPanel.tsx
@@ -225,7 +225,7 @@ export function ChartConfigPanel({ config, queryResult, onConfigChange }: ChartC
{/* 설정 미리보기 */}
📋 설정 미리보기
-
+
X축: {currentConfig.xAxis || '미설정'}
Y축:{' '}
@@ -240,7 +240,7 @@ export function ChartConfigPanel({ config, queryResult, onConfigChange }: ChartC
)}
데이터 행 수: {queryResult.rows.length}개
{Array.isArray(currentConfig.yAxis) && currentConfig.yAxis.length > 1 && (
-
+
✨ 다중 시리즈 차트가 생성됩니다!
)}
@@ -249,7 +249,7 @@ export function ChartConfigPanel({ config, queryResult, onConfigChange }: ChartC
{/* 필수 필드 확인 */}
{(!currentConfig.xAxis || !currentConfig.yAxis) && (
-
+
⚠️ X축과 Y축을 모두 설정해야 차트가 표시됩니다.
diff --git a/frontend/components/admin/dashboard/DashboardCanvas.tsx b/frontend/components/admin/dashboard/DashboardCanvas.tsx
index ed0b253f..0b15e59f 100644
--- a/frontend/components/admin/dashboard/DashboardCanvas.tsx
+++ b/frontend/components/admin/dashboard/DashboardCanvas.tsx
@@ -75,7 +75,7 @@ export const DashboardCanvas = forwardRef
(
w-full min-h-full relative
bg-gray-100
bg-grid-pattern
- ${isDragOver ? 'bg-blue-50' : ''}
+ ${isDragOver ? 'bg-accent' : ''}
`}
style={{
backgroundImage: `
diff --git a/frontend/components/admin/dashboard/DashboardDesigner.tsx b/frontend/components/admin/dashboard/DashboardDesigner.tsx
index e13209f1..60d03747 100644
--- a/frontend/components/admin/dashboard/DashboardDesigner.tsx
+++ b/frontend/components/admin/dashboard/DashboardDesigner.tsx
@@ -207,7 +207,7 @@ export default function DashboardDesigner() {
return (
-
+
대시보드 로딩 중...
잠시만 기다려주세요
@@ -221,7 +221,7 @@ export default function DashboardDesigner() {
{/* 편집 중인 대시보드 표시 */}
{dashboardTitle && (
-
+
📝 편집 중: {dashboardTitle}
)}
diff --git a/frontend/components/admin/dashboard/DashboardSidebar.tsx b/frontend/components/admin/dashboard/DashboardSidebar.tsx
index a10cbc7c..619f0dba 100644
--- a/frontend/components/admin/dashboard/DashboardSidebar.tsx
+++ b/frontend/components/admin/dashboard/DashboardSidebar.tsx
@@ -31,7 +31,7 @@ export function DashboardSidebar() {
type="chart"
subtype="bar"
onDragStart={handleDragStart}
- className="border-l-4 border-blue-500"
+ className="border-l-4 border-primary"
/>
{element.title} 설정
-
+
데이터 소스와 차트 설정을 구성하세요
×
@@ -89,7 +89,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
className={`
px-6 py-3 text-sm font-medium border-b-2 transition-colors
${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'}
`}
>
@@ -100,7 +100,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
className={`
px-6 py-3 text-sm font-medium border-b-2 transition-colors
${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'}
`}
>
@@ -147,7 +147,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
취소
@@ -155,7 +155,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave }: Element
onClick={handleSave}
disabled={!dataSource.query || (!chartConfig.xAxis || !chartConfig.yAxis)}
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
"
>
diff --git a/frontend/components/admin/dashboard/QueryEditor.tsx b/frontend/components/admin/dashboard/QueryEditor.tsx
index 671024cd..5aa70a80 100644
--- a/frontend/components/admin/dashboard/QueryEditor.tsx
+++ b/frontend/components/admin/dashboard/QueryEditor.tsx
@@ -153,7 +153,7 @@ ORDER BY Q4 DESC;`
onClick={executeQuery}
disabled={isExecuting || !query.trim()}
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
flex items-center gap-1
"
@@ -172,10 +172,10 @@ ORDER BY Q4 DESC;`
{/* 샘플 쿼리 버튼들 */}
-
샘플 쿼리:
+
샘플 쿼리:
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"
>
🔥 제품 비교
@@ -224,7 +224,7 @@ ORDER BY Q4 DESC;`
{/* 새로고침 간격 설정 */}
-
자동 새로고침:
+
자동 새로고침: