refactor: 전체 프론트엔드 하드코딩 색상 → CSS 변수 일괄 치환

447+ 파일, 4500+ 줄 변경:
- gray-* → border/bg-muted/text-foreground/text-muted-foreground
- blue-* → primary/ring
- red-* → destructive
- green-* → emerald (일관성)
- indigo-* → primary
- yellow/orange → amber (통일)
- dark mode 변형도 시맨틱 토큰으로 변환

Made-with: Cursor
This commit is contained in:
DDD1542 2026-03-09 14:31:59 +09:00
parent d967cf0a0d
commit 4f10b5e42d
447 changed files with 4520 additions and 4520 deletions

View File

@ -69,33 +69,33 @@ const RESOURCE_TYPE_CONFIG: Record<
string,
{ label: string; icon: React.ElementType; color: string }
> = {
MENU: { label: "메뉴", icon: Layout, color: "bg-blue-100 text-blue-700" },
MENU: { label: "메뉴", icon: Layout, color: "bg-primary/10 text-primary" },
SCREEN: { label: "화면", icon: Monitor, color: "bg-purple-100 text-purple-700" },
SCREEN_LAYOUT: { label: "레이아웃", icon: Monitor, color: "bg-purple-100 text-purple-700" },
FLOW: { label: "플로우", icon: GitBranch, color: "bg-green-100 text-green-700" },
FLOW_STEP: { label: "플로우 스텝", icon: GitBranch, color: "bg-green-100 text-green-700" },
USER: { label: "사용자", icon: User, color: "bg-orange-100 text-orange-700" },
ROLE: { label: "권한", icon: Shield, color: "bg-red-100 text-red-700" },
PERMISSION: { label: "권한", icon: Shield, color: "bg-red-100 text-red-700" },
FLOW: { label: "플로우", icon: GitBranch, color: "bg-emerald-100 text-emerald-700" },
FLOW_STEP: { label: "플로우 스텝", icon: GitBranch, color: "bg-emerald-100 text-emerald-700" },
USER: { label: "사용자", icon: User, color: "bg-amber-100 text-orange-700" },
ROLE: { label: "권한", icon: Shield, color: "bg-destructive/10 text-destructive" },
PERMISSION: { label: "권한", icon: Shield, color: "bg-destructive/10 text-destructive" },
COMPANY: { label: "회사", icon: Building2, color: "bg-indigo-100 text-indigo-700" },
CODE_CATEGORY: { label: "코드 카테고리", icon: Hash, color: "bg-cyan-100 text-cyan-700" },
CODE: { label: "코드", icon: Hash, color: "bg-cyan-100 text-cyan-700" },
DATA: { label: "데이터", icon: Database, color: "bg-gray-100 text-gray-700" },
TABLE: { label: "테이블", icon: Database, color: "bg-gray-100 text-gray-700" },
DATA: { label: "데이터", icon: Database, color: "bg-muted text-foreground" },
TABLE: { label: "테이블", icon: Database, color: "bg-muted text-foreground" },
NUMBERING_RULE: { label: "채번 규칙", icon: FileText, color: "bg-amber-100 text-amber-700" },
BATCH: { label: "배치", icon: RefreshCw, color: "bg-teal-100 text-teal-700" },
};
const ACTION_CONFIG: Record<string, { label: string; color: string }> = {
CREATE: { label: "생성", color: "bg-emerald-100 text-emerald-700" },
UPDATE: { label: "수정", color: "bg-blue-100 text-blue-700" },
DELETE: { label: "삭제", color: "bg-red-100 text-red-700" },
UPDATE: { label: "수정", color: "bg-primary/10 text-primary" },
DELETE: { label: "삭제", color: "bg-destructive/10 text-destructive" },
COPY: { label: "복사", color: "bg-violet-100 text-violet-700" },
LOGIN: { label: "로그인", color: "bg-gray-100 text-gray-700" },
LOGIN: { label: "로그인", color: "bg-muted text-foreground" },
STATUS_CHANGE: { label: "상태변경", color: "bg-amber-100 text-amber-700" },
BATCH_CREATE: { label: "배치생성", color: "bg-emerald-100 text-emerald-700" },
BATCH_UPDATE: { label: "배치수정", color: "bg-blue-100 text-blue-700" },
BATCH_DELETE: { label: "배치삭제", color: "bg-red-100 text-red-700" },
BATCH_UPDATE: { label: "배치수정", color: "bg-primary/10 text-primary" },
BATCH_DELETE: { label: "배치삭제", color: "bg-destructive/10 text-destructive" },
};
function formatDateTime(dateStr: string): string {
@ -203,12 +203,12 @@ function renderChanges(changes: Record<string, unknown>) {
<tr className="bg-muted/50">
<th className="px-3 py-1.5 text-left font-medium"></th>
{hasBefore && (
<th className="px-3 py-1.5 text-left font-medium text-red-600">
<th className="px-3 py-1.5 text-left font-medium text-destructive">
</th>
)}
{hasAfter && (
<th className="px-3 py-1.5 text-left font-medium text-blue-600">
<th className="px-3 py-1.5 text-left font-medium text-primary">
</th>
)}
@ -234,7 +234,7 @@ function renderChanges(changes: Record<string, unknown>) {
{hasBefore && (
<td className="px-3 py-1.5">
{row.beforeVal !== null ? (
<span className="rounded bg-red-50 px-1.5 py-0.5 text-red-700">
<span className="rounded bg-destructive/10 px-1.5 py-0.5 text-destructive">
{row.beforeVal}
</span>
) : (
@ -245,7 +245,7 @@ function renderChanges(changes: Record<string, unknown>) {
{hasAfter && (
<td className="px-3 py-1.5">
{row.afterVal !== null ? (
<span className="rounded bg-blue-50 px-1.5 py-0.5 text-blue-700">
<span className="rounded bg-primary/10 px-1.5 py-0.5 text-primary">
{row.afterVal}
</span>
) : (

View File

@ -311,10 +311,10 @@ export default function BatchCreatePage() {
{/* 매핑 설정 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* FROM 섹션 */}
<Card className="border-green-200">
<CardHeader className="bg-green-50">
<CardTitle className="text-green-700">FROM ( )</CardTitle>
<p className="text-sm text-green-600">
<Card className="border-emerald-200">
<CardHeader className="bg-emerald-50">
<CardTitle className="text-emerald-700">FROM ( )</CardTitle>
<p className="text-sm text-emerald-600">
1단계: 커넥션을 2단계: 테이블을 3단계: 컬럼을
</p>
</CardHeader>
@ -365,7 +365,7 @@ export default function BatchCreatePage() {
{/* FROM 컬럼 목록 */}
{fromTable && (
<div className="space-y-2">
<Label className="text-blue-600 font-semibold">{fromTable} </Label>
<Label className="text-primary font-semibold">{fromTable} </Label>
<div className="border rounded-lg p-4 max-h-80 overflow-y-auto space-y-2">
{fromColumns.map((column) => (
<div
@ -373,16 +373,16 @@ export default function BatchCreatePage() {
onClick={() => handleFromColumnClick(column)}
className={`p-3 border rounded cursor-pointer transition-colors ${
selectedFromColumn?.column_name === column.column_name
? 'bg-green-100 border-green-300'
: 'hover:bg-gray-50 border-gray-200'
? 'bg-emerald-100 border-green-300'
: 'hover:bg-muted border-border'
}`}
>
<div className="font-medium">{column.column_name}</div>
<div className="text-sm text-gray-500">{column.data_type}</div>
<div className="text-sm text-muted-foreground">{column.data_type}</div>
</div>
))}
{fromColumns.length === 0 && fromTable && (
<div className="text-center text-gray-500 py-4">
<div className="text-center text-muted-foreground py-4">
...
</div>
)}
@ -393,10 +393,10 @@ export default function BatchCreatePage() {
</Card>
{/* TO 섹션 */}
<Card className="border-red-200">
<CardHeader className="bg-red-50">
<CardTitle className="text-red-700">TO ( )</CardTitle>
<p className="text-sm text-red-600">
<Card className="border-destructive/20">
<CardHeader className="bg-destructive/10">
<CardTitle className="text-destructive">TO ( )</CardTitle>
<p className="text-sm text-destructive">
FROM에서 ,
</p>
</CardHeader>
@ -447,7 +447,7 @@ export default function BatchCreatePage() {
{/* TO 컬럼 목록 */}
{toTable && (
<div className="space-y-2">
<Label className="text-blue-600 font-semibold">{toTable} </Label>
<Label className="text-primary font-semibold">{toTable} </Label>
<div className="border rounded-lg p-4 max-h-80 overflow-y-auto space-y-2">
{toColumns.map((column) => (
<div
@ -455,16 +455,16 @@ export default function BatchCreatePage() {
onClick={() => handleToColumnClick(column)}
className={`p-3 border rounded cursor-pointer transition-colors ${
selectedFromColumn
? 'hover:bg-red-50 border-gray-200'
: 'bg-gray-100 border-gray-300 cursor-not-allowed'
? 'hover:bg-destructive/10 border-border'
: 'bg-muted border-input cursor-not-allowed'
}`}
>
<div className="font-medium">{column.column_name}</div>
<div className="text-sm text-gray-500">{column.data_type}</div>
<div className="text-sm text-muted-foreground">{column.data_type}</div>
</div>
))}
{toColumns.length === 0 && toTable && (
<div className="text-center text-gray-500 py-4">
<div className="text-center text-muted-foreground py-4">
...
</div>
)}
@ -484,22 +484,22 @@ export default function BatchCreatePage() {
<CardContent>
<div className="space-y-3">
{mappings.map((mapping, index) => (
<div key={index} className="flex items-center justify-between p-4 border rounded-lg bg-yellow-50">
<div key={index} className="flex items-center justify-between p-4 border rounded-lg bg-amber-50">
<div className="flex items-center space-x-4">
<div className="text-sm">
<div className="font-medium">
{mapping.from_table_name}.{mapping.from_column_name}
</div>
<div className="text-gray-500">
<div className="text-muted-foreground">
{mapping.from_column_type}
</div>
</div>
<ArrowRight className="h-4 w-4 text-gray-400" />
<ArrowRight className="h-4 w-4 text-muted-foreground/70" />
<div className="text-sm">
<div className="font-medium">
{mapping.to_table_name}.{mapping.to_column_name}
</div>
<div className="text-gray-500">
<div className="text-muted-foreground">
{mapping.to_column_type}
</div>
</div>
@ -508,7 +508,7 @@ export default function BatchCreatePage() {
variant="ghost"
size="sm"
onClick={() => removeMapping(index)}
className="text-red-600 hover:text-red-700"
className="text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4" />
</Button>

View File

@ -815,7 +815,7 @@ export default function BatchEditPage() {
</SelectContent>
</Select>
)}
<p className="mt-1 text-xs text-gray-500">
<p className="mt-1 text-xs text-muted-foreground">
{authTokenMode === "direct"
? "API 호출 시 Authorization 헤더에 사용할 토큰을 입력하세요."
: "auth_tokens 테이블에서 선택한 서비스의 최신 토큰을 사용합니다."}
@ -874,7 +874,7 @@ export default function BatchEditPage() {
onChange={(e) => setDataArrayPath(e.target.value)}
placeholder="response (예: data.items, results)"
/>
<p className="mt-1 text-xs text-gray-500">
<p className="mt-1 text-xs text-muted-foreground">
API . .
<br />
예시: response, data.items, result.list
@ -902,7 +902,7 @@ export default function BatchEditPage() {
className="min-h-[100px]"
rows={5}
/>
<p className="mt-1 text-xs text-gray-500">API JSON .</p>
<p className="mt-1 text-xs text-muted-foreground">API JSON .</p>
</div>
)}
@ -910,7 +910,7 @@ export default function BatchEditPage() {
<div className="space-y-4">
<div className="border-t pt-4">
<Label className="text-base font-medium">API </Label>
<p className="mt-1 text-sm text-gray-600"> .</p>
<p className="mt-1 text-sm text-muted-foreground"> .</p>
</div>
<div>
@ -967,26 +967,26 @@ export default function BatchEditPage() {
}
/>
{apiParamSource === "dynamic" && (
<p className="mt-1 text-xs text-gray-500">
<p className="mt-1 text-xs text-muted-foreground">
. : {"{{user_id}}"} ID
</p>
)}
</div>
{apiParamType === "url" && (
<div className="rounded-lg bg-blue-50 p-3">
<div className="text-sm font-medium text-blue-800">URL </div>
<div className="mt-1 text-sm text-blue-700">
<div className="rounded-lg bg-primary/10 p-3">
<div className="text-sm font-medium text-primary">URL </div>
<div className="mt-1 text-sm text-primary">
: /api/users/{`{${apiParamName || "userId"}}`}
</div>
<div className="text-sm text-blue-700"> : /api/users/{apiParamValue || "123"}</div>
<div className="text-sm text-primary"> : /api/users/{apiParamValue || "123"}</div>
</div>
)}
{apiParamType === "query" && (
<div className="rounded-lg bg-green-50 p-3">
<div className="text-sm font-medium text-green-800"> </div>
<div className="mt-1 text-sm text-green-700">
<div className="rounded-lg bg-emerald-50 p-3">
<div className="text-sm font-medium text-emerald-800"> </div>
<div className="mt-1 text-sm text-emerald-700">
: {mappings[0]?.from_table_name || "/api/users"}?{apiParamName || "userId"}=
{apiParamValue || "123"}
</div>

View File

@ -294,7 +294,7 @@ export default function FlowEditorPage() {
onNodeDragStop={handleNodeDragStop}
nodeTypes={nodeTypes}
fitView
className="bg-gray-50"
className="bg-muted"
>
<Background />
<Controls />

View File

@ -996,7 +996,7 @@ export default function FlowManagementPage() {
<div className="flex flex-col">
<span className="font-medium">{table.displayName || table.tableName}</span>
{table.description && (
<span className="text-[10px] text-gray-500">{table.description}</span>
<span className="text-[10px] text-muted-foreground">{table.description}</span>
)}
</div>
</CommandItem>

View File

@ -409,7 +409,7 @@ example2@example.com,김철수,XYZ회사`;
{recipients.length > 0 && (
<div className="rounded-md border bg-muted p-4">
<div className="flex items-center gap-2 text-sm">
<CheckCircle2 className="h-4 w-4 text-green-600" />
<CheckCircle2 className="h-4 w-4 text-emerald-600" />
<span className="font-medium">{recipients.length} </span>
</div>
<p className="mt-1 text-xs text-muted-foreground">

View File

@ -101,8 +101,8 @@ export default function MailDashboardPage() {
value: stats.totalAccounts,
icon: Users,
color: "blue",
bgColor: "bg-blue-100",
iconColor: "text-blue-600",
bgColor: "bg-primary/10",
iconColor: "text-primary",
href: "/admin/mail/accounts",
},
{
@ -110,8 +110,8 @@ export default function MailDashboardPage() {
value: stats.totalTemplates,
icon: FileText,
color: "green",
bgColor: "bg-green-100",
iconColor: "text-green-600",
bgColor: "bg-emerald-100",
iconColor: "text-emerald-600",
href: "/admin/mail/templates",
},
{
@ -119,8 +119,8 @@ export default function MailDashboardPage() {
value: stats.sentToday,
icon: Send,
color: "orange",
bgColor: "bg-orange-100",
iconColor: "text-orange-600",
bgColor: "bg-amber-100",
iconColor: "text-amber-600",
href: "/admin/mail/sent",
},
{

View File

@ -438,8 +438,8 @@ export default function MailReceivePage() {
<div
className={`mt-4 p-3 rounded-lg flex items-center gap-2 ${
testResult.success
? "bg-green-50 text-green-800 border border-green-200"
: "bg-red-50 text-red-800 border border-red-200"
? "bg-emerald-50 text-emerald-800 border border-emerald-200"
: "bg-destructive/10 text-red-800 border border-destructive/20"
}`}
>
{testResult.success ? (
@ -460,7 +460,7 @@ export default function MailReceivePage() {
<div className="flex flex-col md:flex-row gap-3">
{/* 검색 */}
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground/70" />
<input
type="text"
value={searchTerm}
@ -511,7 +511,7 @@ export default function MailReceivePage() {
{filteredAndSortedMails.length}
{searchTerm && (
<span className="ml-2">
(: <span className="font-medium text-orange-600">{searchTerm}</span>)
(: <span className="font-medium text-amber-600">{searchTerm}</span>)
</span>
)}
</div>
@ -527,14 +527,14 @@ export default function MailReceivePage() {
{loading ? (
<Card className="">
<CardContent 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-amber-500" />
<span className="ml-3 text-muted-foreground"> ...</span>
</CardContent>
</Card>
) : filteredAndSortedMails.length === 0 ? (
<Card className="text-center py-16 bg-card ">
<CardContent className="pt-6">
<Mail className="w-16 h-16 mx-auto mb-4 text-gray-300" />
<Mail className="w-16 h-16 mx-auto mb-4 text-muted-foreground/50" />
<p className="text-muted-foreground mb-4">
{!selectedAccountId
? "메일 계정을 선택하세요"
@ -560,9 +560,9 @@ export default function MailReceivePage() {
</Card>
) : (
<Card className="">
<CardHeader className="bg-gradient-to-r from-slate-50 to-gray-50 border-b">
<CardHeader className="bg-gradient-to-r from-slate-50 to-muted border-b">
<CardTitle className="flex items-center gap-2">
<Inbox className="w-5 h-5 text-orange-500" />
<Inbox className="w-5 h-5 text-amber-500" />
({filteredAndSortedMails.length}/{mails.length})
</CardTitle>
</CardHeader>
@ -573,14 +573,14 @@ export default function MailReceivePage() {
key={mail.id}
onClick={() => handleMailClick(mail)}
className={`p-4 hover:bg-background transition-colors cursor-pointer ${
!mail.isRead ? "bg-blue-50/30" : ""
!mail.isRead ? "bg-primary/10/30" : ""
} ${selectedMailId === mail.id ? "bg-accent border-l-4 border-l-primary" : ""}`}
>
<div className="flex items-start gap-4">
{/* 읽음 표시 */}
<div className="flex-shrink-0 w-2 h-2 mt-2">
{!mail.isRead && (
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
<div className="w-2 h-2 bg-primary rounded-full"></div>
)}
</div>
@ -598,7 +598,7 @@ export default function MailReceivePage() {
</span>
<div className="flex items-center gap-2">
{mail.hasAttachments && (
<Paperclip className="w-4 h-4 text-gray-400" />
<Paperclip className="w-4 h-4 text-muted-foreground/70" />
)}
<span className="text-xs text-muted-foreground">
{formatDate(mail.date)}
@ -882,14 +882,14 @@ export default function MailReceivePage() {
) : loadingDetail ? (
<Card className="sticky top-6">
<CardContent 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-amber-500" />
<span className="ml-3 text-muted-foreground"> ...</span>
</CardContent>
</Card>
) : (
<Card className="sticky top-6">
<CardContent className="flex flex-col justify-center items-center py-16 text-center">
<Mail className="w-16 h-16 mb-4 text-gray-300" />
<Mail className="w-16 h-16 mb-4 text-muted-foreground/50" />
<p className="text-muted-foreground">
</p>
@ -900,10 +900,10 @@ export default function MailReceivePage() {
</div>
{/* 안내 정보 */}
<Card className="bg-gradient-to-r from-green-50 to-emerald-50 border-green-200 ">
<Card className="bg-gradient-to-r from-green-50 to-emerald-50 border-emerald-200 ">
<CardHeader>
<CardTitle className="text-lg flex items-center">
<CheckCircle className="w-5 h-5 mr-2 text-green-600" />
<CheckCircle className="w-5 h-5 mr-2 text-emerald-600" />
! 🎉
</CardTitle>
</CardHeader>
@ -913,81 +913,81 @@ export default function MailReceivePage() {
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<p className="font-medium text-gray-800 mb-2">📬 </p>
<p className="font-medium text-foreground mb-2">📬 </p>
<ul className="space-y-1 text-sm text-muted-foreground">
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span>IMAP </span>
</li>
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span> </span>
</li>
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span>/ </span>
</li>
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span> </span>
</li>
</ul>
</div>
<div>
<p className="font-medium text-gray-800 mb-2">📄 </p>
<p className="font-medium text-foreground mb-2">📄 </p>
<ul className="space-y-1 text-sm text-muted-foreground">
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span>HTML </span>
</li>
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span> </span>
</li>
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span> </span>
</li>
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span> </span>
</li>
</ul>
</div>
<div>
<p className="font-medium text-gray-800 mb-2">🔍 </p>
<p className="font-medium text-foreground mb-2">🔍 </p>
<ul className="space-y-1 text-sm text-muted-foreground">
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span> (//)</span>
</li>
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span> (/)</span>
</li>
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span> (/)</span>
</li>
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span> (30)</span>
</li>
</ul>
</div>
<div>
<p className="font-medium text-gray-800 mb-2">🔒 </p>
<p className="font-medium text-foreground mb-2">🔒 </p>
<ul className="space-y-1 text-sm text-muted-foreground">
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span>XSS (DOMPurify)</span>
</li>
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span> </span>
</li>
<li className="flex items-start">
<span className="text-green-500 mr-2"></span>
<span className="text-emerald-500 mr-2"></span>
<span> </span>
</li>
</ul>

View File

@ -516,12 +516,12 @@ ${data.originalBody}`;
toast({
title: (
<div className="flex items-center gap-2">
<CheckCircle2 className="w-5 h-5 text-green-500" />
<CheckCircle2 className="w-5 h-5 text-emerald-500" />
<span> !</span>
</div>
) as any,
description: `${to.length}${cc.length > 0 ? ` (참조 ${cc.length}명)` : ""}${bcc.length > 0 ? ` (숨은참조 ${bcc.length}명)` : ""}${attachments.length > 0 ? ` (첨부파일 ${attachments.length}개)` : ""}에게 메일이 성공적으로 발송되었습니다.`,
className: "border-green-500 bg-green-50",
className: "border-emerald-500 bg-emerald-50",
});
// 알림 갱신 이벤트 발생
@ -544,7 +544,7 @@ ${data.originalBody}`;
toast({
title: (
<div className="flex items-center gap-2">
<AlertCircle className="w-5 h-5 text-red-500" />
<AlertCircle className="w-5 h-5 text-destructive" />
<span> </span>
</div>
) as any,
@ -781,7 +781,7 @@ ${data.originalBody}`;
</>
) : (
<>
<CheckCircle2 className="w-4 h-4 text-green-500" />
<CheckCircle2 className="w-4 h-4 text-emerald-500" />
<span>
{new Date(lastSaved).toLocaleTimeString('ko-KR', {
hour: '2-digit',
@ -895,7 +895,7 @@ ${data.originalBody}`;
{to.map((email) => (
<div
key={email}
className="flex items-center gap-1 px-2 py-1 bg-blue-100 text-blue-700 rounded-md text-sm"
className="flex items-center gap-1 px-2 py-1 bg-primary/10 text-primary rounded-md text-sm"
>
<span>{email}</span>
<button
@ -933,12 +933,12 @@ ${data.originalBody}`;
{cc.map((email) => (
<div
key={email}
className="flex items-center gap-1 px-2 py-1 bg-green-100 text-green-700 rounded-md text-sm"
className="flex items-center gap-1 px-2 py-1 bg-emerald-100 text-emerald-700 rounded-md text-sm"
>
<span>{email}</span>
<button
onClick={() => removeEmail(email, "cc")}
className="hover:bg-green-200 rounded p-0.5"
className="hover:bg-emerald-200 rounded p-0.5"
>
<X className="w-3 h-3" />
</button>
@ -1222,7 +1222,7 @@ ${data.originalBody}`;
<div
key={component.id}
style={{ height: `${component.height || 20}px` }}
className="bg-background rounded flex items-center justify-center text-xs text-gray-400"
className="bg-background rounded flex items-center justify-center text-xs text-muted-foreground/70"
>
</div>
@ -1236,7 +1236,7 @@ ${data.originalBody}`;
{component.logoSrc && <img src={component.logoSrc} alt="로고" className="h-10" />}
<span className="font-bold text-lg">{component.brandName}</span>
</div>
<span className="text-sm text-gray-500">{component.sendDate}</span>
<span className="text-sm text-muted-foreground">{component.sendDate}</span>
</div>
</div>
);
@ -1245,13 +1245,13 @@ ${data.originalBody}`;
return (
<div key={component.id} className="border rounded-lg overflow-hidden">
{component.tableTitle && (
<div className="bg-gray-50 px-4 py-2 font-semibold border-b">{component.tableTitle}</div>
<div className="bg-muted px-4 py-2 font-semibold border-b">{component.tableTitle}</div>
)}
<table className="w-full">
<tbody>
{component.rows?.map((row: any, i: number) => (
<tr key={i} className={i % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
<td className="px-4 py-2 font-medium text-gray-600 w-1/3 border-r">{row.label}</td>
<tr key={i} className={i % 2 === 0 ? 'bg-white' : 'bg-muted'}>
<td className="px-4 py-2 font-medium text-muted-foreground w-1/3 border-r">{row.label}</td>
<td className="px-4 py-2">{row.value}</td>
</tr>
))}
@ -1263,9 +1263,9 @@ ${data.originalBody}`;
case 'alertBox':
return (
<div key={component.id} className={`p-4 rounded-lg border-l-4 ${
component.alertType === 'info' ? 'bg-blue-50 border-blue-500 text-blue-800' :
component.alertType === 'info' ? 'bg-primary/10 border-primary text-primary' :
component.alertType === 'warning' ? 'bg-amber-50 border-amber-500 text-amber-800' :
component.alertType === 'danger' ? 'bg-red-50 border-red-500 text-red-800' :
component.alertType === 'danger' ? 'bg-destructive/10 border-destructive text-red-800' :
'bg-emerald-50 border-emerald-500 text-emerald-800'
}`}>
{component.alertTitle && <div className="font-bold mb-1">{component.alertTitle}</div>}
@ -1275,13 +1275,13 @@ ${data.originalBody}`;
case 'divider':
return (
<hr key={component.id} className="border-gray-300" style={{ borderWidth: `${component.height || 1}px` }} />
<hr key={component.id} className="border-input" style={{ borderWidth: `${component.height || 1}px` }} />
);
case 'footer':
return (
<div key={component.id} className="text-center text-sm text-gray-500 py-4 border-t bg-gray-50">
{component.companyName && <div className="font-semibold text-gray-700">{component.companyName}</div>}
<div key={component.id} className="text-center text-sm text-muted-foreground py-4 border-t bg-muted">
{component.companyName && <div className="font-semibold text-foreground">{component.companyName}</div>}
{(component.ceoName || component.businessNumber) && (
<div className="mt-1">
{component.ceoName && <span>: {component.ceoName}</span>}
@ -1297,7 +1297,7 @@ ${data.originalBody}`;
{component.email && <span>Email: {component.email}</span>}
</div>
)}
{component.copyright && <div className="mt-2 text-xs text-gray-400">{component.copyright}</div>}
{component.copyright && <div className="mt-2 text-xs text-muted-foreground/70">{component.copyright}</div>}
</div>
);
@ -1318,9 +1318,9 @@ ${data.originalBody}`;
}
})}
</div>
<div className="bg-gradient-to-r from-green-50 to-emerald-50 px-4 py-3 border-t border-green-200">
<p className="text-sm text-green-800 flex items-center gap-2 font-medium">
<CheckCircle2 className="w-4 h-4 text-green-600" />
<div className="bg-gradient-to-r from-green-50 to-emerald-50 px-4 py-3 border-t border-emerald-200">
<p className="text-sm text-emerald-800 flex items-center gap-2 font-medium">
<CheckCircle2 className="w-4 h-4 text-emerald-600" />
</p>
</div>
@ -1396,7 +1396,7 @@ ${data.originalBody}`;
onChange={handleFileSelect}
className="hidden"
/>
<Upload className="w-12 h-12 mx-auto text-gray-400 mb-3" />
<Upload className="w-12 h-12 mx-auto text-muted-foreground/70 mb-3" />
<p className="text-sm text-muted-foreground mb-1">
</p>
@ -1430,7 +1430,7 @@ ${data.originalBody}`;
variant="ghost"
size="sm"
onClick={() => removeFile(index)}
className="flex-shrink-0 text-red-500 hover:text-red-600 hover:bg-red-50"
className="flex-shrink-0 text-destructive hover:text-destructive hover:bg-destructive/10"
>
<X className="w-4 h-4" />
</Button>
@ -1530,7 +1530,7 @@ ${data.originalBody}`;
<div key={index} className="flex items-center gap-2 text-xs text-muted-foreground">
<File className="w-3 h-3" />
<span className="truncate">{file.name}</span>
<span className="text-gray-400">({formatFileSize(file.size)})</span>
<span className="text-muted-foreground/70">({formatFileSize(file.size)})</span>
</div>
))}
</div>

View File

@ -371,8 +371,8 @@ export default function SentMailPage() {
{stats.successCount}
</p>
</div>
<div className="p-3 bg-green-500/10 rounded-lg">
<CheckCircle2 className="w-6 h-6 text-green-600" />
<div className="p-3 bg-emerald-500/10 rounded-lg">
<CheckCircle2 className="w-6 h-6 text-emerald-600" />
</div>
</div>
</CardContent>
@ -387,8 +387,8 @@ export default function SentMailPage() {
{stats.failedCount}
</p>
</div>
<div className="p-3 bg-red-500/10 rounded-lg">
<XCircle className="w-6 h-6 text-red-600" />
<div className="p-3 bg-destructive/10 rounded-lg">
<XCircle className="w-6 h-6 text-destructive" />
</div>
</div>
</CardContent>
@ -403,8 +403,8 @@ export default function SentMailPage() {
{stats.todayCount}
</p>
</div>
<div className="p-3 bg-blue-500/10 rounded-lg">
<Calendar className="w-6 h-6 text-blue-600" />
<div className="p-3 bg-primary/10 rounded-lg">
<Calendar className="w-6 h-6 text-primary" />
</div>
</div>
</CardContent>

View File

@ -592,19 +592,19 @@ export default function BatchManagementNewPage() {
<div
key={option.value}
className={`cursor-pointer rounded-lg border p-3 transition-all ${
batchType === option.value ? "border-blue-500 bg-blue-50" : "border-gray-200 hover:border-gray-300"
batchType === option.value ? "border-primary bg-primary/10" : "border-border hover:border-input"
}`}
onClick={() => setBatchType(option.value)}
>
<div className="flex items-center space-x-2">
{option.value === "restapi-to-db" ? (
<Globe className="h-4 w-4 text-blue-600" />
<Globe className="h-4 w-4 text-primary" />
) : (
<Database className="h-4 w-4 text-green-600" />
<Database className="h-4 w-4 text-emerald-600" />
)}
<div>
<div className="text-sm font-medium">{option.label}</div>
<div className="mt-1 text-xs text-gray-500">{option.description}</div>
<div className="mt-1 text-xs text-muted-foreground">{option.description}</div>
</div>
</div>
</div>
@ -739,7 +739,7 @@ export default function BatchManagementNewPage() {
</SelectContent>
</Select>
)}
<p className="mt-1 text-xs text-gray-500">
<p className="mt-1 text-xs text-muted-foreground">
{authTokenMode === "direct"
? "API 호출 시 Authorization 헤더에 사용할 토큰을 입력하세요."
: "auth_tokens 테이블에서 선택한 서비스의 최신 토큰을 사용합니다."}
@ -782,7 +782,7 @@ export default function BatchManagementNewPage() {
onChange={(e) => setDataArrayPath(e.target.value)}
placeholder="response (예: data.items, results)"
/>
<p className="mt-1 text-xs text-gray-500">
<p className="mt-1 text-xs text-muted-foreground">
API . .
<br />
예시: response, data.items, result.list
@ -801,7 +801,7 @@ export default function BatchManagementNewPage() {
className="min-h-[100px]"
rows={5}
/>
<p className="mt-1 text-xs text-gray-500">API JSON .</p>
<p className="mt-1 text-xs text-muted-foreground">API JSON .</p>
</div>
)}
@ -809,7 +809,7 @@ export default function BatchManagementNewPage() {
<div className="space-y-4">
<div className="border-t pt-4">
<Label className="text-base font-medium">API </Label>
<p className="mt-1 text-sm text-gray-600"> .</p>
<p className="mt-1 text-sm text-muted-foreground"> .</p>
</div>
<div>
@ -868,26 +868,26 @@ export default function BatchManagementNewPage() {
}
/>
{apiParamSource === "dynamic" && (
<p className="mt-1 text-xs text-gray-500">
<p className="mt-1 text-xs text-muted-foreground">
. : {"{{user_id}}"} ID
</p>
)}
</div>
{apiParamType === "url" && (
<div className="rounded-lg bg-blue-50 p-3">
<div className="text-sm font-medium text-blue-800">URL </div>
<div className="mt-1 text-sm text-blue-700">
<div className="rounded-lg bg-primary/10 p-3">
<div className="text-sm font-medium text-primary">URL </div>
<div className="mt-1 text-sm text-primary">
: /api/users/{`{${apiParamName || "userId"}}`}
</div>
<div className="text-sm text-blue-700"> : /api/users/{apiParamValue || "123"}</div>
<div className="text-sm text-primary"> : /api/users/{apiParamValue || "123"}</div>
</div>
)}
{apiParamType === "query" && (
<div className="rounded-lg bg-green-50 p-3">
<div className="text-sm font-medium text-green-800"> </div>
<div className="mt-1 text-sm text-green-700">
<div className="rounded-lg bg-emerald-50 p-3">
<div className="text-sm font-medium text-emerald-800"> </div>
<div className="mt-1 text-sm text-emerald-700">
: {fromEndpoint || "/api/users"}?{apiParamName || "userId"}=
{apiParamValue || "123"}
</div>
@ -899,9 +899,9 @@ export default function BatchManagementNewPage() {
{/* API 호출 미리보기 정보 */}
{fromApiUrl && fromEndpoint && (
<div className="rounded-lg bg-gray-50 p-3">
<div className="text-sm font-medium text-gray-700">API </div>
<div className="mt-1 text-sm text-gray-600">
<div className="rounded-lg bg-muted p-3">
<div className="text-sm font-medium text-foreground">API </div>
<div className="mt-1 text-sm text-muted-foreground">
{fromApiMethod} {fromApiUrl}
{apiParamType === "url" && apiParamName && apiParamValue
? fromEndpoint.replace(`{${apiParamName}}`, apiParamValue) || fromEndpoint + `/${apiParamValue}`
@ -911,14 +911,14 @@ export default function BatchManagementNewPage() {
: ""}
</div>
{((authTokenMode === "direct" && fromApiKey) || (authTokenMode === "db" && authServiceName)) && (
<div className="mt-1 text-xs text-gray-500">
<div className="mt-1 text-xs text-muted-foreground">
{authTokenMode === "direct"
? `Authorization: Bearer ${fromApiKey.substring(0, 15)}...`
: `Authorization: DB 토큰 (${authServiceName})`}
</div>
)}
{apiParamType !== "none" && apiParamName && apiParamValue && (
<div className="mt-1 text-xs text-blue-600">
<div className="mt-1 text-xs text-primary">
: {apiParamName} = {apiParamValue} ({apiParamSource === "static" ? "고정값" : "동적값"})
</div>
)}
@ -988,7 +988,7 @@ export default function BatchManagementNewPage() {
setSelectedColumns(selectedColumns.filter((col) => col !== column.column_name));
}
}}
className="rounded border-gray-300"
className="rounded border-input"
/>
<label
htmlFor={`col-${column.column_name}`}
@ -1002,14 +1002,14 @@ export default function BatchManagementNewPage() {
</div>
{/* 선택된 컬럼 개수 표시 */}
<div className="mt-2 text-xs text-gray-500">
<div className="mt-2 text-xs text-muted-foreground">
: {selectedColumns.length} / : {fromColumns.length}
</div>
{/* 빠른 매핑 버튼들 */}
{selectedColumns.length > 0 && toApiFields.length > 0 && (
<div className="mt-3 rounded-lg border border-green-200 bg-green-50 p-3">
<div className="mb-2 text-sm font-medium text-green-800"> </div>
<div className="mt-3 rounded-lg border border-emerald-200 bg-emerald-50 p-3">
<div className="mb-2 text-sm font-medium text-emerald-800"> </div>
<div className="flex flex-wrap gap-2">
<button
type="button"
@ -1051,7 +1051,7 @@ export default function BatchManagementNewPage() {
setDbToApiFieldMapping(mapping);
toast.success(`${Object.keys(mapping).length}개 컬럼이 자동 매핑되었습니다.`);
}}
className="rounded bg-blue-600 px-3 py-1 text-xs text-white hover:bg-blue-700"
className="rounded bg-primary px-3 py-1 text-xs text-white hover:bg-primary/90"
>
</button>
@ -1061,7 +1061,7 @@ export default function BatchManagementNewPage() {
setDbToApiFieldMapping({});
toast.success("매핑이 초기화되었습니다.");
}}
className="rounded bg-gray-600 px-3 py-1 text-xs text-white hover:bg-gray-700"
className="rounded bg-foreground/80 px-3 py-1 text-xs text-white hover:bg-foreground/90"
>
</button>
@ -1071,9 +1071,9 @@ export default function BatchManagementNewPage() {
{/* 자동 생성된 JSON 미리보기 */}
{selectedColumns.length > 0 && (
<div className="mt-3 rounded-lg border border-blue-200 bg-blue-50 p-3">
<div className="mb-2 text-sm font-medium text-blue-800"> JSON </div>
<pre className="overflow-x-auto font-mono text-xs text-blue-600">
<div className="mt-3 rounded-lg border border-primary/20 bg-primary/10 p-3">
<div className="mb-2 text-sm font-medium text-primary"> JSON </div>
<pre className="overflow-x-auto font-mono text-xs text-primary">
{JSON.stringify(
selectedColumns.reduce(
(obj, col) => {
@ -1105,7 +1105,7 @@ export default function BatchManagementNewPage() {
setToApiBody(autoJson);
toast.success("Request Body에 자동 생성된 JSON이 적용되었습니다.");
}}
className="mt-2 rounded bg-blue-600 px-3 py-1 text-xs text-white hover:bg-blue-700"
className="mt-2 rounded bg-primary px-3 py-1 text-xs text-white hover:bg-primary/90"
>
Request Body에
</button>
@ -1231,7 +1231,7 @@ export default function BatchManagementNewPage() {
))}
</SelectContent>
</Select>
<p className="mt-1 text-xs text-gray-500">
<p className="mt-1 text-xs text-muted-foreground">
UPDATE, INSERT . (: device_serial_number)
</p>
</div>
@ -1273,7 +1273,7 @@ export default function BatchManagementNewPage() {
placeholder="/api/users"
/>
{(toApiMethod === "PUT" || toApiMethod === "DELETE") && (
<p className="mt-1 text-xs text-gray-500">
<p className="mt-1 text-xs text-muted-foreground">
URL: {toEndpoint}/{urlPathColumn ? `{${urlPathColumn}}` : "{ID}"}
</p>
)}
@ -1310,7 +1310,7 @@ export default function BatchManagementNewPage() {
))}
</SelectContent>
</Select>
<p className="mt-1 text-xs text-gray-500">
<p className="mt-1 text-xs text-muted-foreground">
PUT/DELETE URL . (: USER_ID /api/users/user123)
</p>
</div>
@ -1321,7 +1321,7 @@ export default function BatchManagementNewPage() {
<button
type="button"
onClick={previewToApiData}
className="flex items-center space-x-2 rounded-md bg-green-600 px-4 py-2 text-white hover:bg-green-700"
className="flex items-center space-x-2 rounded-md bg-emerald-600 px-4 py-2 text-white hover:bg-green-700"
>
<Eye className="h-4 w-4" />
<span>API </span>
@ -1330,13 +1330,13 @@ export default function BatchManagementNewPage() {
{/* TO API 필드 표시 */}
{toApiFields.length > 0 && (
<div className="rounded-lg border border-green-200 bg-green-50 p-3">
<div className="mb-2 text-sm font-medium text-green-800">
<div className="rounded-lg border border-emerald-200 bg-emerald-50 p-3">
<div className="mb-2 text-sm font-medium text-emerald-800">
API ({toApiFields.length})
</div>
<div className="flex flex-wrap gap-2">
{toApiFields.map((field) => (
<span key={field} className="rounded bg-green-100 px-2 py-1 text-xs text-green-700">
<span key={field} className="rounded bg-emerald-100 px-2 py-1 text-xs text-emerald-700">
{field}
</span>
))}
@ -1355,7 +1355,7 @@ export default function BatchManagementNewPage() {
placeholder='{"id": "{{id}}", "name": "{{name}}", "email": "{{email}}"}'
className="h-24 w-full rounded-md border p-2 font-mono text-sm"
/>
<div className="mt-1 text-xs text-gray-500">
<div className="mt-1 text-xs text-muted-foreground">
DB {"{{컬럼명}}"} . : {"{{user_id}}, {{user_name}}"}
</div>
</div>
@ -1363,15 +1363,15 @@ export default function BatchManagementNewPage() {
{/* API 호출 정보 */}
{toApiUrl && toApiKey && toEndpoint && (
<div className="rounded-lg bg-gray-50 p-3">
<div className="text-sm font-medium text-gray-700">API </div>
<div className="mt-1 text-sm text-gray-600">
<div className="rounded-lg bg-muted p-3">
<div className="text-sm font-medium text-foreground">API </div>
<div className="mt-1 text-sm text-muted-foreground">
{toApiMethod} {toApiUrl}
{toEndpoint}
</div>
<div className="mt-1 text-xs text-gray-500">Headers: X-API-Key: {toApiKey.substring(0, 10)}...</div>
<div className="mt-1 text-xs text-muted-foreground">Headers: X-API-Key: {toApiKey.substring(0, 10)}...</div>
{toApiBody && (
<div className="mt-1 text-xs text-blue-600">Body: {toApiBody.substring(0, 50)}...</div>
<div className="mt-1 text-xs text-primary">Body: {toApiBody.substring(0, 50)}...</div>
)}
</div>
)}
@ -1394,7 +1394,7 @@ export default function BatchManagementNewPage() {
</Button>
{(!fromApiUrl || !fromEndpoint || !toTable) && (
<p className="ml-4 flex items-center text-xs text-gray-500">
<p className="ml-4 flex items-center text-xs text-muted-foreground">
FROM TO .
</p>
)}
@ -1693,17 +1693,17 @@ const DbToRestApiMappingCard = memo(function DbToRestApiMappingCard({
<CardContent>
<div className="max-h-96 space-y-3 overflow-y-auto rounded-lg border p-4">
{selectedColumnObjects.map((column) => (
<div key={column.column_name} className="flex items-center space-x-4 rounded-lg bg-gray-50 p-3">
<div key={column.column_name} className="flex items-center space-x-4 rounded-lg bg-muted p-3">
{/* DB 컬럼 정보 */}
<div className="flex-1">
<div className="text-sm font-medium">{column.column_name}</div>
<div className="text-xs text-gray-500">
<div className="text-xs text-muted-foreground">
: {column.data_type} | NULL: {column.is_nullable ? "Y" : "N"}
</div>
</div>
{/* 화살표 */}
<div className="text-gray-400"></div>
<div className="text-muted-foreground/70"></div>
{/* API 필드 선택 드롭다운 */}
<div className="flex-1">
@ -1735,7 +1735,7 @@ const DbToRestApiMappingCard = memo(function DbToRestApiMappingCard({
<input
type="text"
placeholder="API 필드명을 직접 입력하세요"
className="mt-2 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
className="mt-2 w-full rounded-md border border-input px-3 py-2 text-sm focus:ring-2 focus:ring-ring focus:outline-none"
onChange={(e) => {
setDbToApiFieldMapping((prev) => ({
...prev,
@ -1745,7 +1745,7 @@ const DbToRestApiMappingCard = memo(function DbToRestApiMappingCard({
/>
)}
<div className="mt-1 text-xs text-gray-500">
<div className="mt-1 text-xs text-muted-foreground">
{dbToApiFieldMapping[column.column_name]
? `매핑: ${column.column_name}${dbToApiFieldMapping[column.column_name]}`
: `기본값: ${column.column_name} (DB 컬럼명 사용)`}
@ -1755,32 +1755,32 @@ const DbToRestApiMappingCard = memo(function DbToRestApiMappingCard({
{/* 템플릿 미리보기 */}
<div className="flex-1">
<div className="rounded border bg-white p-2 font-mono text-sm">{`{{${column.column_name}}}`}</div>
<div className="mt-1 text-xs text-gray-500"> DB </div>
<div className="mt-1 text-xs text-muted-foreground"> DB </div>
</div>
</div>
))}
</div>
{selectedColumns.length > 0 && (
<div className="mt-4 rounded-lg border border-blue-200 bg-blue-50 p-3">
<div className="text-sm font-medium text-blue-800"> JSON </div>
<pre className="mt-1 overflow-x-auto font-mono text-xs text-blue-600">{autoJsonPreview}</pre>
<div className="mt-4 rounded-lg border border-primary/20 bg-primary/10 p-3">
<div className="text-sm font-medium text-primary"> JSON </div>
<pre className="mt-1 overflow-x-auto font-mono text-xs text-primary">{autoJsonPreview}</pre>
<button
type="button"
onClick={() => {
setToApiBody(autoJsonPreview);
toast.success("Request Body에 자동 생성된 JSON이 적용되었습니다.");
}}
className="mt-2 rounded bg-blue-600 px-3 py-1 text-xs text-white hover:bg-blue-700"
className="mt-2 rounded bg-primary px-3 py-1 text-xs text-white hover:bg-primary/90"
>
Request Body에
</button>
</div>
)}
<div className="mt-4 rounded-lg border border-blue-200 bg-blue-50 p-3">
<div className="text-sm font-medium text-blue-800"> </div>
<div className="mt-1 font-mono text-xs text-blue-600">
<div className="mt-4 rounded-lg border border-primary/20 bg-primary/10 p-3">
<div className="text-sm font-medium text-primary"> </div>
<div className="mt-1 font-mono text-xs text-primary">
{'{"id": "{{id}}", "name": "{{user_name}}", "email": "{{email}}"}'}
</div>
</div>

View File

@ -381,7 +381,7 @@ export default function AutoFillTab() {
<Pencil className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" onClick={() => handleDeleteConfirm(group.groupCode)}>
<Trash2 className="h-4 w-4 text-red-500" />
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</TableCell>
</TableRow>
@ -676,7 +676,7 @@ export default function AutoFillTab() {
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={handleDelete} className="bg-red-500 hover:bg-red-600">
<AlertDialogAction onClick={handleDelete} className="bg-destructive hover:bg-destructive">
</AlertDialogAction>
</AlertDialogFooter>

View File

@ -577,9 +577,9 @@ export default function CascadingRelationsTab() {
</TableCell>
<TableCell>
<div className="flex items-center gap-2 text-sm">
<span className="rounded bg-blue-100 px-2 py-0.5 text-blue-700">{relation.parent_table}</span>
<span className="rounded bg-primary/10 px-2 py-0.5 text-primary">{relation.parent_table}</span>
<ChevronRight className="text-muted-foreground h-4 w-4" />
<span className="rounded bg-green-100 px-2 py-0.5 text-green-700">
<span className="rounded bg-emerald-100 px-2 py-0.5 text-emerald-700">
{relation.child_table}
</span>
</div>
@ -619,7 +619,7 @@ export default function CascadingRelationsTab() {
<div className="space-y-4">
{/* Step 1: 부모 테이블 */}
<div className="rounded-lg border p-4">
<h4 className="mb-3 text-sm font-semibold text-blue-600">1. ( )</h4>
<h4 className="mb-3 text-sm font-semibold text-primary">1. ( )</h4>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<Label className="text-xs"></Label>
@ -696,7 +696,7 @@ export default function CascadingRelationsTab() {
{/* Step 2: 자식 테이블 */}
<div className="rounded-lg border p-4">
<h4 className="mb-3 text-sm font-semibold text-green-600">2. ( )</h4>
<h4 className="mb-3 text-sm font-semibold text-emerald-600">2. ( )</h4>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<Label className="text-xs"></Label>

View File

@ -304,7 +304,7 @@ export default function ConditionTab() {
<TableCell>
<div className="text-sm">
<span className="text-muted-foreground">{condition.conditionField}</span>
<span className="mx-1 text-blue-600">{getOperatorLabel(condition.conditionOperator)}</span>
<span className="mx-1 text-primary">{getOperatorLabel(condition.conditionOperator)}</span>
<span className="font-medium">{condition.conditionValue}</span>
</div>
</TableCell>
@ -329,7 +329,7 @@ export default function ConditionTab() {
size="icon"
onClick={() => handleDeleteConfirm(condition.conditionId!)}
>
<Trash2 className="h-4 w-4 text-red-500" />
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</TableCell>
</TableRow>
@ -491,7 +491,7 @@ export default function ConditionTab() {
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={handleDelete} className="bg-red-500 hover:bg-red-600">
<AlertDialogAction onClick={handleDelete} className="bg-destructive hover:bg-destructive">
</AlertDialogAction>
</AlertDialogFooter>

View File

@ -501,7 +501,7 @@ export default function HierarchyTab() {
<Pencil className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" onClick={() => handleDeleteConfirm(group.groupCode)}>
<Trash2 className="h-4 w-4 text-red-500" />
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</div>
</div>
@ -548,7 +548,7 @@ export default function HierarchyTab() {
size="icon"
onClick={() => handleDeleteLevel(level.levelId!, group.groupCode)}
>
<Trash2 className="h-4 w-4 text-red-500" />
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</TableCell>
</TableRow>
@ -836,7 +836,7 @@ export default function HierarchyTab() {
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={handleDelete} className="bg-red-500 hover:bg-red-600">
<AlertDialogAction onClick={handleDelete} className="bg-destructive hover:bg-destructive">
</AlertDialogAction>
</AlertDialogFooter>

View File

@ -354,7 +354,7 @@ export default function MutualExclusionTab() {
<Pencil className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon" onClick={() => handleDeleteConfirm(exclusion.exclusionId!)}>
<Trash2 className="h-4 w-4 text-red-500" />
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</TableCell>
</TableRow>
@ -404,7 +404,7 @@ export default function MutualExclusionTab() {
/>
{fieldList.length > 2 && (
<Button variant="ghost" size="icon" onClick={() => removeField(index)}>
<Trash2 className="h-4 w-4 text-red-500" />
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
)}
</div>
@ -571,7 +571,7 @@ export default function MutualExclusionTab() {
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={handleDelete} className="bg-red-500 hover:bg-red-600">
<AlertDialogAction onClick={handleDelete} className="bg-destructive hover:bg-destructive">
</AlertDialogAction>
</AlertDialogFooter>

View File

@ -133,7 +133,7 @@ export default function DebugLayoutPage() {
<h1 className="mb-4 text-2xl font-bold"> </h1>
<div className="space-y-4">
<div className="rounded bg-green-100 p-4">
<div className="rounded bg-emerald-100 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<p> : {debugInfo.hasToken ? "✅ 예" : "❌ 아니오"}</p>
<p> : {debugInfo.tokenLength}</p>
@ -142,14 +142,14 @@ export default function DebugLayoutPage() {
<p>SessionStorage : {debugInfo.sessionToken ? "✅ 존재" : "❌ 없음"}</p>
</div>
<div className="rounded bg-blue-100 p-4">
<div className="rounded bg-primary/10 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<p> URL: {debugInfo.currentUrl}</p>
<p>Pathname: {debugInfo.pathname}</p>
<p>: {debugInfo.timestamp}</p>
</div>
<div className="rounded bg-yellow-100 p-4">
<div className="rounded bg-amber-100 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<div className="space-x-2">
<button
@ -157,11 +157,11 @@ export default function DebugLayoutPage() {
const token = localStorage.getItem("authToken");
alert(`토큰: ${token ? "존재" : "없음"}\n길이: ${token ? token.length : 0}`);
}}
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
className="rounded bg-primary px-4 py-2 text-white hover:bg-primary"
>
</button>
<button onClick={handleTokenSync} className="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600">
<button onClick={handleTokenSync} className="rounded bg-emerald-500 px-4 py-2 text-white hover:bg-emerald-600">
</button>
<button
@ -173,13 +173,13 @@ export default function DebugLayoutPage() {
</div>
</div>
<div className="rounded bg-orange-100 p-4">
<div className="rounded bg-amber-100 p-4">
<h2 className="mb-2 font-semibold">API </h2>
<div className="mb-4 space-x-2">
<button onClick={handleApiTest} className="rounded bg-orange-500 px-4 py-2 text-white hover:bg-orange-600">
<button onClick={handleApiTest} className="rounded bg-amber-500 px-4 py-2 text-white hover:bg-orange-600">
API
</button>
<button onClick={handleUserApiTest} className="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600">
<button onClick={handleUserApiTest} className="rounded bg-destructive px-4 py-2 text-white hover:bg-destructive">
API
</button>
<button
@ -194,7 +194,7 @@ export default function DebugLayoutPage() {
<div
className={`rounded p-3 ${
apiTestResult.status === "success"
? "bg-green-200"
? "bg-emerald-200"
: apiTestResult.status === "error"
? "bg-red-200"
: "bg-yellow-200"

View File

@ -28,27 +28,27 @@ export default function SimpleDebugPage() {
<h1 className="mb-4 text-2xl font-bold"> </h1>
<div className="space-y-4">
<div className="rounded bg-green-100 p-4">
<div className="rounded bg-emerald-100 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<p> : {tokenInfo.hasToken ? "✅ 예" : "❌ 아니오"}</p>
<p> : {tokenInfo.tokenLength}</p>
<p> : {tokenInfo.tokenStart}</p>
</div>
<div className="rounded bg-blue-100 p-4">
<div className="rounded bg-primary/10 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<p> URL: {tokenInfo.currentUrl}</p>
<p>: {tokenInfo.timestamp}</p>
</div>
<div className="rounded bg-yellow-100 p-4">
<div className="rounded bg-amber-100 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<button
onClick={() => {
const token = localStorage.getItem("authToken");
alert(`토큰: ${token ? "존재" : "없음"}\n길이: ${token ? token.length : 0}`);
}}
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
className="rounded bg-primary px-4 py-2 text-white hover:bg-primary"
>
</button>

View File

@ -15,16 +15,16 @@ export default function AdminDebugPage() {
<h1 className="mb-4 text-2xl font-bold"> </h1>
<div className="space-y-4">
<div className="rounded bg-gray-100 p-4">
<div className="rounded bg-muted p-4">
<h2 className="mb-2 font-semibold"> </h2>
<p>: {loading ? "예" : "아니오"}</p>
<p>: {isLoggedIn ? "예" : "아니오"}</p>
<p>: {isAdmin ? "예" : "아니오"}</p>
{error && <p className="text-red-500">: {error}</p>}
{error && <p className="text-destructive">: {error}</p>}
</div>
{user && (
<div className="rounded bg-blue-100 p-4">
<div className="rounded bg-primary/10 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<p>ID: {user.userId}</p>
<p>: {user.userName}</p>
@ -34,7 +34,7 @@ export default function AdminDebugPage() {
</div>
)}
<div className="rounded bg-green-100 p-4">
<div className="rounded bg-emerald-100 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<p>
localStorage : {typeof window !== "undefined" && localStorage.getItem("authToken") ? "존재" : "없음"}

View File

@ -220,13 +220,13 @@ export default function LayoutManagementPage() {
};
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-muted">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>
<h1 className="text-3xl font-bold text-gray-900"> </h1>
<p className="mt-2 text-gray-600"> </p>
<h1 className="text-3xl font-bold text-foreground"> </h1>
<p className="mt-2 text-muted-foreground"> </p>
</div>
<Button className="flex items-center gap-2 shadow-sm" onClick={() => setCreateModalOpen(true)}>
<Plus className="h-4 w-4" />
@ -239,7 +239,7 @@ export default function LayoutManagementPage() {
<div className="flex items-center gap-4">
<div className="flex-1">
<div className="relative">
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform text-gray-400" />
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform text-muted-foreground/70" />
<Input
placeholder="레이아웃 이름 또는 설명으로 검색..."
value={searchTerm}
@ -276,7 +276,7 @@ export default function LayoutManagementPage() {
{loading ? (
<div className="py-8 text-center"> ...</div>
) : layouts.length === 0 ? (
<div className="py-8 text-center text-gray-500"> .</div>
<div className="py-8 text-center text-muted-foreground"> .</div>
) : (
<>
{/* 레이아웃 그리드 */}
@ -288,7 +288,7 @@ export default function LayoutManagementPage() {
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="flex items-center gap-2">
<CategoryIcon className="h-5 w-5 text-gray-600" />
<CategoryIcon className="h-5 w-5 text-muted-foreground" />
<Badge variant="secondary">
{CATEGORY_NAMES[layout.category as keyof typeof CATEGORY_NAMES]}
</Badge>
@ -312,7 +312,7 @@ export default function LayoutManagementPage() {
<Copy className="mr-2 h-4 w-4" />
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleDelete(layout)} className="text-red-600">
<DropdownMenuItem onClick={() => handleDelete(layout)} className="text-destructive">
<Trash2 className="mr-2 h-4 w-4" />
</DropdownMenuItem>
@ -321,17 +321,17 @@ export default function LayoutManagementPage() {
</div>
<CardTitle className="text-lg">{layout.layoutName}</CardTitle>
{layout.description && (
<p className="line-clamp-2 text-sm text-gray-600">{layout.description}</p>
<p className="line-clamp-2 text-sm text-muted-foreground">{layout.description}</p>
)}
</CardHeader>
<CardContent>
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-500">:</span>
<span className="text-muted-foreground">:</span>
<Badge variant="outline">{layout.layoutType}</Badge>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-500"> :</span>
<span className="text-muted-foreground"> :</span>
<span>{layout.zonesConfig.length}</span>
</div>
{layout.isPublic === "Y" && (
@ -397,7 +397,7 @@ export default function LayoutManagementPage() {
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} className="bg-red-600 hover:bg-red-700">
<AlertDialogAction onClick={confirmDelete} className="bg-destructive hover:bg-red-700">
</AlertDialogAction>
</AlertDialogFooter>

View File

@ -59,25 +59,25 @@ export default function MonitoringPage() {
const getStatusIcon = (status: string) => {
switch (status) {
case 'completed':
return <CheckCircle className="h-4 w-4 text-green-500" />;
return <CheckCircle className="h-4 w-4 text-emerald-500" />;
case 'failed':
return <AlertCircle className="h-4 w-4 text-red-500" />;
return <AlertCircle className="h-4 w-4 text-destructive" />;
case 'running':
return <Play className="h-4 w-4 text-blue-500" />;
return <Play className="h-4 w-4 text-primary" />;
case 'pending':
return <Clock className="h-4 w-4 text-yellow-500" />;
return <Clock className="h-4 w-4 text-amber-500" />;
default:
return <Clock className="h-4 w-4 text-gray-500" />;
return <Clock className="h-4 w-4 text-muted-foreground" />;
}
};
const getStatusBadge = (status: string) => {
const variants = {
completed: "bg-green-100 text-green-800",
completed: "bg-emerald-100 text-emerald-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",
running: "bg-primary/20 text-primary",
pending: "bg-amber-100 text-yellow-800",
cancelled: "bg-muted text-foreground",
};
const labels = {
@ -120,7 +120,7 @@ export default function MonitoringPage() {
}
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-muted">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 헤더 */}
<div>
@ -191,7 +191,7 @@ export default function MonitoringPage() {
<div className="text-2xl"></div>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">{monitoring.successful_jobs_today}</div>
<div className="text-2xl font-bold text-emerald-600">{monitoring.successful_jobs_today}</div>
<p className="text-xs text-muted-foreground">
: {getSuccessRate()}%
</p>

View File

@ -16,7 +16,7 @@ export default function BarcodeLabelDesignerPage() {
return (
<DndProvider backend={HTML5Backend}>
<BarcodeDesignerProvider labelId={labelId}>
<div className="flex h-screen flex-col overflow-hidden bg-gray-50">
<div className="flex h-screen flex-col overflow-hidden bg-muted">
<BarcodeDesignerToolbar />
<div className="flex flex-1 overflow-hidden">
<BarcodeDesignerLeftPanel />

View File

@ -29,12 +29,12 @@ export default function BarcodeLabelManagementPage() {
};
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-muted">
<div className="w-full max-w-none space-y-8 px-4 py-8">
<div className="flex items-center justify-between rounded-lg border bg-white p-6 shadow-sm">
<div>
<h1 className="text-3xl font-bold text-gray-900"> </h1>
<p className="mt-2 text-gray-600">ZD421 </p>
<h1 className="text-3xl font-bold text-foreground"> </h1>
<p className="mt-2 text-muted-foreground">ZD421 </p>
</div>
<Button onClick={handleCreateNew} className="gap-2">
<Plus className="h-4 w-4" />
@ -43,7 +43,7 @@ export default function BarcodeLabelManagementPage() {
</div>
<Card className="shadow-sm">
<CardHeader className="bg-gray-50/50">
<CardHeader className="bg-muted/50">
<CardTitle className="flex items-center gap-2">
<Search className="h-5 w-5" />
@ -75,7 +75,7 @@ export default function BarcodeLabelManagementPage() {
</Card>
<Card className="shadow-sm">
<CardHeader className="bg-gray-50/50">
<CardHeader className="bg-muted/50">
<CardTitle className="flex items-center justify-between">
<span className="flex items-center gap-2">

View File

@ -67,7 +67,7 @@ export default function ReportDesignerPage() {
return (
<DndProvider backend={HTML5Backend}>
<ReportDesignerProvider reportId={reportId}>
<div className="flex h-screen flex-col overflow-hidden bg-gray-50">
<div className="flex h-screen flex-col overflow-hidden bg-muted">
{/* 상단 툴바 */}
<ReportDesignerToolbar />

View File

@ -30,13 +30,13 @@ export default function ReportManagementPage() {
};
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-muted">
<div className="w-full max-w-none space-y-8 px-4 py-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between rounded-lg border bg-white p-6 shadow-sm">
<div>
<h1 className="text-3xl font-bold text-gray-900"> </h1>
<p className="mt-2 text-gray-600"> </p>
<h1 className="text-3xl font-bold text-foreground"> </h1>
<p className="mt-2 text-muted-foreground"> </p>
</div>
<Button onClick={handleCreateNew} className="gap-2">
<Plus className="h-4 w-4" />
@ -45,7 +45,7 @@ export default function ReportManagementPage() {
{/* 검색 영역 */}
<Card className="shadow-sm">
<CardHeader className="bg-gray-50/50">
<CardHeader className="bg-muted/50">
<CardTitle className="flex items-center gap-2">
<Search className="h-5 w-5" />
@ -78,7 +78,7 @@ export default function ReportManagementPage() {
{/* 리포트 목록 */}
<Card className="shadow-sm">
<CardHeader className="bg-gray-50/50">
<CardHeader className="bg-muted/50">
<CardTitle className="flex items-center justify-between">
<span className="flex items-center gap-2">
📋

View File

@ -204,7 +204,7 @@ export default function EditWebTypePage() {
}
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-muted">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 헤더 */}
<div className="mb-6 flex items-center gap-4">
@ -244,7 +244,7 @@ export default function EditWebTypePage() {
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="type_name">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Input
id="type_name"
@ -267,7 +267,7 @@ export default function EditWebTypePage() {
{/* 카테고리 */}
<div className="space-y-2">
<Label htmlFor="category">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Select value={formData.category || ""} onValueChange={(value) => handleInputChange("category", value)}>
<SelectTrigger>
@ -286,7 +286,7 @@ export default function EditWebTypePage() {
{/* 연결된 컴포넌트 */}
<div className="space-y-2">
<Label htmlFor="component_name">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Select
value={formData.component_name || "TextWidget"}
@ -338,15 +338,15 @@ export default function EditWebTypePage() {
</SelectContent>
</Select>
{formData.config_panel && (
<div className="rounded-lg border border-green-200 bg-green-50 p-3">
<div className="rounded-lg border border-emerald-200 bg-emerald-50 p-3">
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-green-500"></div>
<span className="text-sm font-medium text-green-700">
<div className="h-2 w-2 rounded-full bg-emerald-500"></div>
<span className="text-sm font-medium text-emerald-700">
: {getConfigPanelInfo(formData.config_panel)?.label || formData.config_panel}
</span>
</div>
{getConfigPanelInfo(formData.config_panel)?.description && (
<p className="mt-1 ml-4 text-xs text-green-600">
<p className="mt-1 ml-4 text-xs text-emerald-600">
{getConfigPanelInfo(formData.config_panel)?.description}
</p>
)}
@ -427,7 +427,7 @@ export default function EditWebTypePage() {
rows={4}
className="font-mono text-xs"
/>
{jsonErrors.default_config && <p className="text-xs text-red-500">{jsonErrors.default_config}</p>}
{jsonErrors.default_config && <p className="text-xs text-destructive">{jsonErrors.default_config}</p>}
</div>
{/* 유효성 검사 규칙 */}
@ -441,7 +441,7 @@ export default function EditWebTypePage() {
rows={4}
className="font-mono text-xs"
/>
{jsonErrors.validation_rules && <p className="text-xs text-red-500">{jsonErrors.validation_rules}</p>}
{jsonErrors.validation_rules && <p className="text-xs text-destructive">{jsonErrors.validation_rules}</p>}
</div>
{/* 기본 스타일 */}
@ -455,7 +455,7 @@ export default function EditWebTypePage() {
rows={4}
className="font-mono text-xs"
/>
{jsonErrors.default_style && <p className="text-xs text-red-500">{jsonErrors.default_style}</p>}
{jsonErrors.default_style && <p className="text-xs text-destructive">{jsonErrors.default_style}</p>}
</div>
{/* 입력 속성 */}
@ -469,7 +469,7 @@ export default function EditWebTypePage() {
rows={4}
className="font-mono text-xs"
/>
{jsonErrors.input_properties && <p className="text-xs text-red-500">{jsonErrors.input_properties}</p>}
{jsonErrors.input_properties && <p className="text-xs text-destructive">{jsonErrors.input_properties}</p>}
</div>
</div>
</CardContent>
@ -498,8 +498,8 @@ export default function EditWebTypePage() {
{/* 에러 메시지 */}
{updateError && (
<div className="mt-4 rounded-md border border-red-200 bg-red-50 p-4">
<p className="text-red-600">
<div className="mt-4 rounded-md border border-destructive/20 bg-destructive/10 p-4">
<p className="text-destructive">
: {updateError instanceof Error ? updateError.message : "알 수 없는 오류"}
</p>
</div>

View File

@ -56,7 +56,7 @@ export default function WebTypeDetailPage() {
return (
<div className="flex h-96 items-center justify-center">
<div className="text-center">
<div className="mb-2 text-lg text-red-600"> .</div>
<div className="mb-2 text-lg text-destructive"> .</div>
<Link href="/admin/standards">
<Button variant="outline"> </Button>
</Link>
@ -80,7 +80,7 @@ export default function WebTypeDetailPage() {
}
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-muted">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 헤더 */}
<div className="mb-6 flex items-center justify-between">

View File

@ -159,7 +159,7 @@ export default function NewWebTypePage() {
};
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-muted">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 헤더 */}
<div className="mb-6 flex items-center gap-4">
@ -186,7 +186,7 @@ export default function NewWebTypePage() {
{/* 웹타입 코드 */}
<div className="space-y-2">
<Label htmlFor="web_type">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Input
id="web_type"
@ -202,7 +202,7 @@ export default function NewWebTypePage() {
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="type_name">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Input
id="type_name"
@ -225,7 +225,7 @@ export default function NewWebTypePage() {
{/* 카테고리 */}
<div className="space-y-2">
<Label htmlFor="category">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Select value={formData.category} onValueChange={(value) => handleInputChange("category", value)}>
<SelectTrigger>
@ -244,7 +244,7 @@ export default function NewWebTypePage() {
{/* 연결된 컴포넌트 */}
<div className="space-y-2">
<Label htmlFor="component_name">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Select
value={formData.component_name || "TextWidget"}
@ -296,15 +296,15 @@ export default function NewWebTypePage() {
</SelectContent>
</Select>
{formData.config_panel && (
<div className="rounded-lg border border-green-200 bg-green-50 p-3">
<div className="rounded-lg border border-emerald-200 bg-emerald-50 p-3">
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-green-500"></div>
<span className="text-sm font-medium text-green-700">
<div className="h-2 w-2 rounded-full bg-emerald-500"></div>
<span className="text-sm font-medium text-emerald-700">
: {getConfigPanelInfo(formData.config_panel)?.label || formData.config_panel}
</span>
</div>
{getConfigPanelInfo(formData.config_panel)?.description && (
<p className="mt-1 ml-4 text-xs text-green-600">
<p className="mt-1 ml-4 text-xs text-emerald-600">
{getConfigPanelInfo(formData.config_panel)?.description}
</p>
)}
@ -385,7 +385,7 @@ export default function NewWebTypePage() {
rows={4}
className="font-mono text-xs"
/>
{jsonErrors.default_config && <p className="text-xs text-red-500">{jsonErrors.default_config}</p>}
{jsonErrors.default_config && <p className="text-xs text-destructive">{jsonErrors.default_config}</p>}
</div>
{/* 유효성 검사 규칙 */}
@ -399,7 +399,7 @@ export default function NewWebTypePage() {
rows={4}
className="font-mono text-xs"
/>
{jsonErrors.validation_rules && <p className="text-xs text-red-500">{jsonErrors.validation_rules}</p>}
{jsonErrors.validation_rules && <p className="text-xs text-destructive">{jsonErrors.validation_rules}</p>}
</div>
{/* 기본 스타일 */}
@ -413,7 +413,7 @@ export default function NewWebTypePage() {
rows={4}
className="font-mono text-xs"
/>
{jsonErrors.default_style && <p className="text-xs text-red-500">{jsonErrors.default_style}</p>}
{jsonErrors.default_style && <p className="text-xs text-destructive">{jsonErrors.default_style}</p>}
</div>
{/* 입력 속성 */}
@ -427,7 +427,7 @@ export default function NewWebTypePage() {
rows={4}
className="font-mono text-xs"
/>
{jsonErrors.input_properties && <p className="text-xs text-red-500">{jsonErrors.input_properties}</p>}
{jsonErrors.input_properties && <p className="text-xs text-destructive">{jsonErrors.input_properties}</p>}
</div>
</div>
</CardContent>
@ -448,8 +448,8 @@ export default function NewWebTypePage() {
{/* 에러 메시지 */}
{createError && (
<div className="mt-4 rounded-md border border-red-200 bg-red-50 p-4">
<p className="text-red-600">
<div className="mt-4 rounded-md border border-destructive/20 bg-destructive/10 p-4">
<p className="text-destructive">
: {createError instanceof Error ? createError.message : "알 수 없는 오류"}
</p>
</div>

View File

@ -142,28 +142,28 @@ export default function CollectionManagementPage() {
const getStatusBadge = (isActive: string) => {
return isActive === "Y" ? (
<Badge className="bg-green-100 text-green-800"></Badge>
<Badge className="bg-emerald-100 text-emerald-800"></Badge>
) : (
<Badge className="bg-red-100 text-red-800"></Badge>
<Badge className="bg-destructive/10 text-red-800"></Badge>
);
};
const getTypeBadge = (type: string) => {
const option = collectionTypeOptions.find(opt => opt.value === type);
const colors = {
full: "bg-blue-100 text-blue-800",
full: "bg-primary/10 text-primary",
incremental: "bg-purple-100 text-purple-800",
delta: "bg-orange-100 text-orange-800",
delta: "bg-amber-100 text-orange-800",
};
return (
<Badge className={colors[type as keyof typeof colors] || "bg-gray-100 text-gray-800"}>
<Badge className={colors[type as keyof typeof colors] || "bg-muted text-foreground"}>
{option?.label || type}
</Badge>
);
};
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-muted">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 헤더 */}
<div className="flex items-center justify-between">

View File

@ -51,8 +51,8 @@ export default function DataFlowEditPage() {
return (
<div className="flex h-64 items-center justify-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>
<p className="text-gray-500"> ...</p>
<div className="mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-b-2 border-primary"></div>
<p className="text-muted-foreground"> ...</p>
</div>
</div>
);
@ -68,16 +68,16 @@ export default function DataFlowEditPage() {
<span></span>
</Button>
<div>
<h1 className="text-2xl font-bold text-gray-900">📊 </h1>
<p className="mt-1 text-gray-600">
<span className="font-medium text-blue-600">{diagramName}</span>
<h1 className="text-2xl font-bold text-foreground">📊 </h1>
<p className="mt-1 text-muted-foreground">
<span className="font-medium text-primary">{diagramName}</span>
</p>
</div>
</div>
</div>
{/* 데이터플로우 디자이너 */}
<div className="rounded-lg border border-gray-200 bg-white">
<div className="rounded-lg border border-border bg-white">
<DataFlowDesigner
key={diagramId}
selectedDiagram={diagramName}

View File

@ -17,8 +17,8 @@ export default function NodeEditorPage() {
}, [router]);
return (
<div className="flex h-screen items-center justify-center bg-gray-50">
<div className="text-gray-500"> ...</div>
<div className="flex h-screen items-center justify-center bg-muted">
<div className="text-muted-foreground"> ...</div>
</div>
);
}

View File

@ -528,14 +528,14 @@ export default function I18nPage() {
? "공통"
: companies.find((c) => c.code === row.original.companyCode)?.name || row.original.companyCode;
return <span className={row.original.isActive === "N" ? "text-gray-400" : ""}>{companyName}</span>;
return <span className={row.original.isActive === "N" ? "text-muted-foreground/70" : ""}>{companyName}</span>;
},
},
{
accessorKey: "menuName",
header: "메뉴명",
cell: ({ row }: any) => (
<span className={row.original.isActive === "N" ? "text-gray-400" : ""}>{row.original.menuName}</span>
<span className={row.original.isActive === "N" ? "text-muted-foreground/70" : ""}>{row.original.menuName}</span>
),
},
{
@ -543,8 +543,8 @@ export default function I18nPage() {
header: "언어 키",
cell: ({ row }: any) => (
<div
className={`cursor-pointer rounded p-1 hover:bg-gray-100 ${
row.original.isActive === "N" ? "text-gray-400" : ""
className={`cursor-pointer rounded p-1 hover:bg-muted ${
row.original.isActive === "N" ? "text-muted-foreground/70" : ""
}`}
onDoubleClick={() => handleEditKey(row.original)}
>
@ -556,7 +556,7 @@ export default function I18nPage() {
accessorKey: "description",
header: "설명",
cell: ({ row }: any) => (
<span className={row.original.isActive === "N" ? "text-gray-400" : ""}>{row.original.description}</span>
<span className={row.original.isActive === "N" ? "text-muted-foreground/70" : ""}>{row.original.description}</span>
),
},
{
@ -567,8 +567,8 @@ export default function I18nPage() {
onClick={() => handleToggleStatus(row.original.keyId)}
className={`rounded px-2 py-1 text-xs font-medium transition-colors ${
row.original.isActive === "Y"
? "bg-green-100 text-green-800 hover:bg-green-200"
: "bg-gray-100 text-gray-800 hover:bg-gray-200"
? "bg-emerald-100 text-emerald-800 hover:bg-emerald-200"
: "bg-muted text-foreground hover:bg-muted/80"
}`}
>
{row.original.isActive === "Y" ? "활성" : "비활성"}
@ -605,8 +605,8 @@ export default function I18nPage() {
header: "언어 코드",
cell: ({ row }: any) => (
<div
className={`cursor-pointer rounded p-1 hover:bg-gray-100 ${
row.original.isActive === "N" ? "text-gray-400" : ""
className={`cursor-pointer rounded p-1 hover:bg-muted ${
row.original.isActive === "N" ? "text-muted-foreground/70" : ""
}`}
onDoubleClick={() => handleEditLanguage(row.original)}
>
@ -618,14 +618,14 @@ export default function I18nPage() {
accessorKey: "langName",
header: "언어명 (영문)",
cell: ({ row }: any) => (
<span className={row.original.isActive === "N" ? "text-gray-400" : ""}>{row.original.langName}</span>
<span className={row.original.isActive === "N" ? "text-muted-foreground/70" : ""}>{row.original.langName}</span>
),
},
{
accessorKey: "langNative",
header: "언어명 (원어)",
cell: ({ row }: any) => (
<span className={row.original.isActive === "N" ? "text-gray-400" : ""}>{row.original.langNative}</span>
<span className={row.original.isActive === "N" ? "text-muted-foreground/70" : ""}>{row.original.langNative}</span>
),
},
{
@ -636,8 +636,8 @@ export default function I18nPage() {
onClick={() => handleToggleLanguageStatus(row.original.langCode)}
className={`rounded px-2 py-1 text-xs font-medium transition-colors ${
row.original.isActive === "Y"
? "bg-green-100 text-green-800 hover:bg-green-200"
: "bg-gray-100 text-gray-800 hover:bg-gray-200"
? "bg-emerald-100 text-emerald-800 hover:bg-emerald-200"
: "bg-muted text-foreground hover:bg-muted/80"
}`}
>
{row.original.isActive === "Y" ? "활성" : "비활성"}
@ -651,7 +651,7 @@ export default function I18nPage() {
}
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-muted">
<div className="w-full max-w-none px-4 py-8">
<div className="container mx-auto p-2">
{/* 탭 네비게이션 */}
@ -659,7 +659,7 @@ export default function I18nPage() {
<button
onClick={() => setActiveTab("keys")}
className={`rounded-t-lg px-3 py-1.5 text-sm font-medium transition-colors ${
activeTab === "keys" ? "bg-accent0 text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200"
activeTab === "keys" ? "bg-accent0 text-white" : "bg-muted text-foreground hover:bg-muted/80"
}`}
>
@ -667,7 +667,7 @@ export default function I18nPage() {
<button
onClick={() => setActiveTab("languages")}
className={`rounded-t-lg px-3 py-1.5 text-sm font-medium transition-colors ${
activeTab === "languages" ? "bg-accent0 text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200"
activeTab === "languages" ? "bg-accent0 text-white" : "bg-muted text-foreground hover:bg-muted/80"
}`}
>
@ -854,7 +854,7 @@ export default function I18nPage() {
</div>
</div>
) : (
<div className="flex h-64 items-center justify-center text-gray-500">
<div className="flex h-64 items-center justify-center text-muted-foreground">
<div className="text-center">
<div className="mb-2 text-lg font-medium"> </div>
<div className="text-sm"> </div>

View File

@ -120,12 +120,12 @@ export default function TemplatesManagePage() {
// 간단한 아이콘 매핑 (실제로는 더 복잡한 시스템 필요)
const iconMap: Record<string, JSX.Element> = {
table: <div className="h-4 w-4 border border-gray-400" />,
"mouse-pointer": <div className="h-4 w-4 rounded bg-blue-500" />,
upload: <div className="h-4 w-4 border-2 border-dashed border-gray-400" />,
table: <div className="h-4 w-4 border border-input" />,
"mouse-pointer": <div className="h-4 w-4 rounded bg-primary" />,
upload: <div className="h-4 w-4 border-2 border-dashed border-input" />,
};
return iconMap[iconName] || <div className="h-4 w-4 rounded bg-gray-300" />;
return iconMap[iconName] || <div className="h-4 w-4 rounded bg-muted/60" />;
};
if (error) {
@ -133,7 +133,7 @@ export default function TemplatesManagePage() {
<div className="w-full max-w-none px-4 py-8">
<Card>
<CardContent className="flex flex-col items-center justify-center py-8">
<p className="mb-4 text-red-600">릿 .</p>
<p className="mb-4 text-destructive">릿 .</p>
<Button onClick={() => refetch()} variant="outline">
<RefreshCw className="mr-2 h-4 w-4" />
@ -145,13 +145,13 @@ export default function TemplatesManagePage() {
}
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-muted">
<div className="w-full max-w-none px-4 py-8 space-y-8">
{/* 페이지 제목 */}
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
<div>
<h1 className="text-3xl font-bold text-gray-900">릿 </h1>
<p className="mt-2 text-gray-600"> 릿 </p>
<h1 className="text-3xl font-bold text-foreground">릿 </h1>
<p className="mt-2 text-muted-foreground"> 릿 </p>
</div>
<div className="flex space-x-2">
<Button asChild className="shadow-sm">
@ -164,9 +164,9 @@ export default function TemplatesManagePage() {
{/* 필터 및 검색 */}
<Card className="shadow-sm">
<CardHeader className="bg-gray-50/50">
<CardHeader className="bg-muted/50">
<CardTitle className="flex items-center">
<Filter className="mr-2 h-5 w-5 text-gray-600" />
<Filter className="mr-2 h-5 w-5 text-muted-foreground" />
</CardTitle>
</CardHeader>
@ -176,7 +176,7 @@ export default function TemplatesManagePage() {
<div className="space-y-2">
<label className="text-sm font-medium"></label>
<div className="relative">
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground/70" />
<Input
placeholder="템플릿명, 설명 검색..."
value={searchTerm}
@ -232,7 +232,7 @@ export default function TemplatesManagePage() {
{/* 템플릿 목록 테이블 */}
<Card className="shadow-sm">
<CardHeader className="bg-gray-50/50">
<CardHeader className="bg-muted/50">
<CardTitle>릿 ({filteredAndSortedTemplates.length})</CardTitle>
</CardHeader>
<CardContent>
@ -293,7 +293,7 @@ export default function TemplatesManagePage() {
</TableRow>
) : filteredAndSortedTemplates.length === 0 ? (
<TableRow>
<TableCell colSpan={11} className="py-8 text-center text-gray-500">
<TableCell colSpan={11} className="py-8 text-center text-muted-foreground">
릿 .
</TableCell>
</TableRow>
@ -357,7 +357,7 @@ export default function TemplatesManagePage() {
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size="sm" variant="ghost" className="text-red-600 hover:text-red-700">
<Button size="sm" variant="ghost" className="text-destructive hover:text-destructive">
<Trash2 className="h-4 w-4" />
</Button>
</AlertDialogTrigger>
@ -373,7 +373,7 @@ export default function TemplatesManagePage() {
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction
onClick={() => handleDelete(template.template_code, template.template_name)}
className="bg-red-600 hover:bg-red-700"
className="bg-destructive hover:bg-red-700"
disabled={isDeleting}
>

View File

@ -25,27 +25,27 @@ export default function TestPage() {
<h1 className="mb-4 text-2xl font-bold"> </h1>
<div className="space-y-4">
<div className="rounded bg-green-100 p-4">
<div className="rounded bg-emerald-100 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<p> : {tokenInfo.hasToken ? "✅ 예" : "❌ 아니오"}</p>
<p> : {tokenInfo.tokenLength}</p>
<p> : {tokenInfo.tokenStart}</p>
</div>
<div className="rounded bg-blue-100 p-4">
<div className="rounded bg-primary/10 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<p> URL: {tokenInfo.currentUrl}</p>
<p>: {tokenInfo.timestamp}</p>
</div>
<div className="rounded bg-yellow-100 p-4">
<div className="rounded bg-amber-100 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<button
onClick={() => {
const token = localStorage.getItem("authToken");
alert(`토큰: ${token ? "존재" : "없음"}\n길이: ${token ? token.length : 0}`);
}}
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
className="rounded bg-primary px-4 py-2 text-white hover:bg-primary"
>
</button>

View File

@ -31,12 +31,12 @@ export default function TokenTestPage() {
<h1 className="mb-4 text-2xl font-bold"> </h1>
<div className="space-y-4">
<div className="rounded bg-gray-100 p-4">
<div className="rounded bg-muted p-4">
<h2 className="mb-2 font-semibold"> </h2>
<pre className="text-sm">{JSON.stringify(tokenInfo, null, 2)}</pre>
</div>
<div className="rounded bg-blue-100 p-4">
<div className="rounded bg-primary/10 p-4">
<h2 className="mb-2 font-semibold"> </h2>
<button
onClick={() => {
@ -44,7 +44,7 @@ export default function TokenTestPage() {
console.log("현재 토큰:", token);
alert(`토큰: ${token ? "존재" : "없음"}`);
}}
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
className="rounded bg-primary px-4 py-2 text-white hover:bg-primary"
>
</button>

View File

@ -259,7 +259,7 @@ export default function RoleDetailPage({ params }: { params: Promise<{ id: strin
</div>
<span
className={`rounded-full px-3 py-1 text-sm font-medium ${
roleGroup.status === "active" ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"
roleGroup.status === "active" ? "bg-emerald-100 text-emerald-800" : "bg-muted text-foreground"
}`}
>
{roleGroup.status === "active" ? "활성" : "비활성"}

View File

@ -273,7 +273,7 @@ export default function RolesPage() {
</div>
<span
className={`rounded-full px-2 py-1 text-xs font-medium ${
role.status === "active" ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"
role.status === "active" ? "bg-emerald-100 text-emerald-800" : "bg-muted text-foreground"
}`}
>
{role.status === "active" ? "활성" : "비활성"}

View File

@ -388,7 +388,7 @@ export default function ValidationDemoPage() {
<CardDescription> . .</CardDescription>
</CardHeader>
<CardContent>
<div className="relative min-h-[400px] rounded-lg border border-dashed border-gray-300 p-4">
<div className="relative min-h-[400px] rounded-lg border border-dashed border-input p-4">
<EnhancedInteractiveScreenViewer
component={TEST_COMPONENTS[0]} // container
allComponents={TEST_COMPONENTS}
@ -485,7 +485,7 @@ export default function ValidationDemoPage() {
<div className="space-y-2">
<h4 className="font-semibold"> </h4>
<pre className="max-h-60 overflow-auto rounded-md bg-gray-100 p-3 text-sm">
<pre className="max-h-60 overflow-auto rounded-md bg-muted p-3 text-sm">
{JSON.stringify(formData, null, 2)}
</pre>
</div>
@ -493,15 +493,15 @@ export default function ValidationDemoPage() {
<div className="space-y-2">
<h4 className="font-semibold"> </h4>
<div className="grid grid-cols-2 gap-4">
<div className="rounded-md bg-green-50 p-3">
<div className="text-lg font-bold text-green-600">
<div className="rounded-md bg-emerald-50 p-3">
<div className="text-lg font-bold text-emerald-600">
{Object.values(validationState.fieldStates).filter((f) => f.status === "valid").length}
</div>
<div className="text-sm text-green-700"> </div>
<div className="text-sm text-emerald-700"> </div>
</div>
<div className="rounded-md bg-red-50 p-3">
<div className="text-lg font-bold text-red-600">{validationState.errors.length}</div>
<div className="text-sm text-red-700"> </div>
<div className="rounded-md bg-destructive/10 p-3">
<div className="text-lg font-bold text-destructive">{validationState.errors.length}</div>
<div className="text-sm text-destructive"> </div>
</div>
</div>
</div>

View File

@ -29,7 +29,7 @@ const statusConfig: Record<string, { label: string; variant: "default" | "second
const lineStatusConfig: Record<string, { label: string; icon: React.ReactNode }> = {
waiting: { label: "대기", icon: <Clock className="h-3 w-3 text-muted-foreground" /> },
pending: { label: "진행 중", icon: <Clock className="h-3 w-3 text-primary" /> },
approved: { label: "승인", icon: <CheckCircle2 className="h-3 w-3 text-green-600" /> },
approved: { label: "승인", icon: <CheckCircle2 className="h-3 w-3 text-emerald-600" /> },
rejected: { label: "반려", icon: <XCircle className="h-3 w-3 text-destructive" /> },
skipped: { label: "건너뜀", icon: <Clock className="h-3 w-3 text-muted-foreground" /> },
};

View File

@ -107,18 +107,18 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
return (
<div className="h-screen">
{/* 대시보드 헤더 - 보기 모드에서는 숨김 */}
{/* <div className="border-b border-gray-200 bg-white px-6 py-4">
{/* <div className="border-b border-border bg-white px-6 py-4">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-800">{dashboard.title}</h1>
{dashboard.description && <p className="mt-1 text-sm text-gray-600">{dashboard.description}</p>}
<h1 className="text-2xl font-bold text-foreground">{dashboard.title}</h1>
{dashboard.description && <p className="mt-1 text-sm text-muted-foreground">{dashboard.description}</p>}
</div>
<div className="flex items-center gap-3">
{/* *\/}
<button
onClick={loadDashboard}
className="rounded-lg border border-gray-300 px-3 py-2 text-gray-600 hover:bg-gray-50 hover:text-gray-800"
className="rounded-lg border border-input px-3 py-2 text-muted-foreground hover:bg-muted hover:text-foreground"
title="새로고침"
>
🔄
@ -133,7 +133,7 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
document.documentElement.requestFullscreen();
}
}}
className="rounded-lg border border-gray-300 px-3 py-2 text-gray-600 hover:bg-gray-50 hover:text-gray-800"
className="rounded-lg border border-input px-3 py-2 text-muted-foreground hover:bg-muted hover:text-foreground"
title="전체화면"
>
@ -144,7 +144,7 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
onClick={() => {
router.push(`/admin/screenMng/dashboardList?load=${resolvedParams.dashboardId}`);
}}
className="rounded-lg bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
className="rounded-lg bg-primary px-4 py-2 text-white hover:bg-primary"
>
</button>
@ -152,7 +152,7 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
</div>
{/* *\/}
<div className="mt-2 flex items-center gap-4 text-xs text-gray-500">
<div className="mt-2 flex items-center gap-4 text-xs text-muted-foreground">
<span>: {new Date(dashboard.createdAt).toLocaleString()}</span>
<span>: {new Date(dashboard.updatedAt).toLocaleString()}</span>
<span>: {dashboard.elements.length}</span>

View File

@ -5,7 +5,7 @@ import { useAuth } from "@/hooks/useAuth";
import { FileCheck, Menu, Users, Bell, FileText, Layout, Server, Shield, Calendar } from "lucide-react";
const quickAccessItems = [
{ label: "결재함", icon: FileCheck, href: "/admin/approvalBox", color: "text-blue-600 bg-blue-50" },
{ label: "결재함", icon: FileCheck, href: "/admin/approvalBox", color: "text-primary bg-primary/10" },
{ label: "메뉴 관리", icon: Menu, href: "/admin/menu", color: "text-violet-600 bg-violet-50" },
{ label: "사용자 관리", icon: Users, href: "/admin/userMng", color: "text-emerald-600 bg-emerald-50" },
{ label: "공지사항", icon: Bell, href: "/admin/system-notices", color: "text-amber-600 bg-amber-50" },

View File

@ -385,13 +385,13 @@ export default function MultiLangPage() {
</div>
<div className="flex items-end">
<div className="text-sm text-gray-600"> : {filteredLangKeys.length}</div>
<div className="text-sm text-muted-foreground"> : {filteredLangKeys.length}</div>
</div>
</div>
</div>
<div className="mb-4 flex items-center justify-between">
<div className="text-sm text-gray-600">: {filteredLangKeys.length}</div>
<div className="text-sm text-muted-foreground">: {filteredLangKeys.length}</div>
</div>
<DataTable
@ -417,7 +417,7 @@ export default function MultiLangPage() {
{languages.map((lang) => (
<div key={lang.langCode} className="rounded-lg border p-4">
<div className="font-semibold">{lang.langName}</div>
<div className="text-sm text-gray-600">{lang.langNative}</div>
<div className="text-sm text-muted-foreground">{lang.langNative}</div>
<Badge variant={lang.isActive === "Y" ? "default" : "secondary"} className="mt-2">
{lang.isActive === "Y" ? "활성" : "비활성"}
</Badge>
@ -440,7 +440,7 @@ export default function MultiLangPage() {
<h3 className="mb-2 font-semibold">{company.name}</h3>
<div className="space-y-1">
{menus.map((menu) => (
<div key={menu.code} className="text-sm text-gray-600">
<div key={menu.code} className="text-sm text-muted-foreground">
{menu.name}
</div>
))}

View File

@ -5,7 +5,7 @@ import { useAuth } from "@/hooks/useAuth";
import { FileCheck, Menu, Users, Bell, FileText, Layout, Server, Shield, Calendar, ArrowRight } from "lucide-react";
const quickAccessItems = [
{ label: "결재함", icon: FileCheck, href: "/admin/approvalBox", color: "text-blue-600 bg-blue-50" },
{ label: "결재함", icon: FileCheck, href: "/admin/approvalBox", color: "text-primary bg-primary/10" },
{ label: "메뉴 관리", icon: Menu, href: "/admin/menu", color: "text-violet-600 bg-violet-50" },
{ label: "사용자 관리", icon: Users, href: "/admin/userMng", color: "text-emerald-600 bg-emerald-50" },
{ label: "공지사항", icon: Bell, href: "/admin/system-notices", color: "text-amber-600 bg-amber-50" },

View File

@ -174,10 +174,10 @@ function PopScreenViewPage() {
if (loading) {
return (
<div className="flex h-screen w-full items-center justify-center bg-gray-100">
<div className="flex h-screen w-full items-center justify-center bg-muted">
<div className="text-center">
<Loader2 className="mx-auto h-10 w-10 animate-spin text-blue-500" />
<p className="mt-4 text-gray-600">POP ...</p>
<Loader2 className="mx-auto h-10 w-10 animate-spin text-primary" />
<p className="mt-4 text-muted-foreground">POP ...</p>
</div>
</div>
);
@ -185,13 +185,13 @@ function PopScreenViewPage() {
if (error || !screen) {
return (
<div className="flex h-screen w-full items-center justify-center bg-gray-100">
<div className="flex h-screen w-full items-center justify-center bg-muted">
<div className="text-center max-w-md p-6">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-red-100">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-destructive/10">
<span className="text-2xl">!</span>
</div>
<h2 className="mb-2 text-xl font-bold text-gray-800"> </h2>
<p className="mb-4 text-gray-600">{error || "요청하신 POP 화면이 존재하지 않습니다."}</p>
<h2 className="mb-2 text-xl font-bold text-foreground"> </h2>
<p className="mb-4 text-muted-foreground">{error || "요청하신 POP 화면이 존재하지 않습니다."}</p>
<Button onClick={() => router.back()} variant="outline">
<ArrowLeft className="mr-2 h-4 w-4" />
@ -205,7 +205,7 @@ function PopScreenViewPage() {
<ScreenPreviewProvider isPreviewMode={isPreviewMode}>
<ActiveTabProvider>
<TableOptionsProvider>
<div className="h-screen bg-gray-100 flex flex-col">
<div className="h-screen bg-muted flex flex-col">
{/* 상단 툴바 (프리뷰 모드에서만) */}
{isPreviewMode && (
<div className="sticky top-0 z-50 bg-white border-b shadow-sm">
@ -216,13 +216,13 @@ function PopScreenViewPage() {
</Button>
<span className="text-sm font-medium">{screen.screenName}</span>
<span className="text-xs text-gray-400">
<span className="text-xs text-muted-foreground/70">
({currentModeKey.replace("_", " ")})
</span>
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 bg-gray-100 rounded-lg p-1">
<div className="flex items-center gap-1 bg-muted rounded-lg p-1">
<Button
variant={deviceType === "mobile" ? "default" : "ghost"}
size="sm"
@ -243,7 +243,7 @@ function PopScreenViewPage() {
</Button>
</div>
<div className="flex items-center gap-1 bg-gray-100 rounded-lg p-1">
<div className="flex items-center gap-1 bg-muted rounded-lg p-1">
<Button
variant={isLandscape ? "default" : "ghost"}
size="sm"
@ -295,7 +295,7 @@ function PopScreenViewPage() {
)}
<div
className={`bg-white transition-all duration-300 ${isPreviewMode ? "shadow-2xl rounded-3xl overflow-auto border-8 border-gray-800" : "w-full min-h-full"}`}
className={`bg-white transition-all duration-300 ${isPreviewMode ? "shadow-2xl rounded-3xl overflow-auto border-8 border-foreground" : "w-full min-h-full"}`}
style={isPreviewMode ? {
width: currentDevice.width,
maxHeight: "80vh",
@ -333,13 +333,13 @@ function PopScreenViewPage() {
) : (
// 빈 화면
<div className="flex flex-col items-center justify-center min-h-[400px] p-8 text-center">
<div className="w-16 h-16 rounded-full bg-gray-100 flex items-center justify-center mb-4">
<Smartphone className="h-8 w-8 text-gray-400" />
<div className="w-16 h-16 rounded-full bg-muted flex items-center justify-center mb-4">
<Smartphone className="h-8 w-8 text-muted-foreground/70" />
</div>
<h3 className="text-lg font-semibold text-gray-800 mb-2">
<h3 className="text-lg font-semibold text-foreground mb-2">
</h3>
<p className="text-sm text-gray-500 max-w-xs">
<p className="text-sm text-muted-foreground max-w-xs">
POP .
</p>
</div>

View File

@ -227,9 +227,9 @@ export default function SimpleTypeSafetyTest() {
</div>
{passedTests === totalTests && totalTests > 0 && (
<div className="mt-4 rounded-lg border border-green-200 bg-green-50 p-4">
<div className="font-medium text-green-800">🎉 !</div>
<div className="mt-2 text-sm text-green-600">
<div className="mt-4 rounded-lg border border-emerald-200 bg-emerald-50 p-4">
<div className="font-medium text-emerald-800">🎉 !</div>
<div className="mt-2 text-sm text-emerald-600">
, , .
</div>
</div>

View File

@ -138,7 +138,7 @@ export default function StressTestPage() {
<div className="space-y-4 text-center">
<h1 className="text-3xl font-bold">🔥 </h1>
<p className="text-muted-foreground"> </p>
<div className="rounded-lg bg-orange-50 p-3 text-sm text-orange-600">
<div className="rounded-lg bg-amber-50 p-3 text-sm text-amber-600">
주의:
</div>
@ -184,19 +184,19 @@ export default function StressTestPage() {
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
<div className="text-center">
<div className="text-2xl font-bold text-green-600">{testResults.passedTests}</div>
<div className="text-2xl font-bold text-emerald-600">{testResults.passedTests}</div>
<div className="text-muted-foreground text-sm"></div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-red-600">{testResults.failedTests}</div>
<div className="text-2xl font-bold text-destructive">{testResults.failedTests}</div>
<div className="text-muted-foreground text-sm"></div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-yellow-600">{testResults.warningTests}</div>
<div className="text-2xl font-bold text-amber-600">{testResults.warningTests}</div>
<div className="text-muted-foreground text-sm"></div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-blue-600">{Math.round(testResults.totalDuration)}ms</div>
<div className="text-2xl font-bold text-primary">{Math.round(testResults.totalDuration)}ms</div>
<div className="text-muted-foreground text-sm"> </div>
</div>
</div>
@ -212,16 +212,16 @@ export default function StressTestPage() {
</div>
{testResults.success ? (
<div className="rounded-lg border border-green-200 bg-green-50 p-4">
<div className="font-medium text-green-800">🎉 !</div>
<div className="mt-2 text-sm text-green-600">
<div className="rounded-lg border border-emerald-200 bg-emerald-50 p-4">
<div className="font-medium text-emerald-800">🎉 !</div>
<div className="mt-2 text-sm text-emerald-600">
.
</div>
</div>
) : (
<div className="rounded-lg border border-red-200 bg-red-50 p-4">
<div className="rounded-lg border border-destructive/20 bg-destructive/10 p-4">
<div className="font-medium text-red-800"> </div>
<div className="mt-2 text-sm text-red-600">
<div className="mt-2 text-sm text-destructive">
. .
</div>
</div>
@ -253,7 +253,7 @@ export default function StressTestPage() {
{/* 메트릭스 표시 */}
{result.metrics && (
<div className="mt-3 rounded bg-gray-50 p-3 text-xs">
<div className="mt-3 rounded bg-muted p-3 text-xs">
<div className="mb-1 font-medium">📊 :</div>
<div className="grid grid-cols-2 gap-2">
{Object.entries(result.metrics).map(([key, value]) => (
@ -286,7 +286,7 @@ export default function StressTestPage() {
<ul className="space-y-2">
{testResults.recommendation.map((rec, index) => (
<li key={index} className="flex items-start gap-2">
<span className="mt-0.5 text-blue-500"></span>
<span className="mt-0.5 text-primary"></span>
<span className="text-sm">{rec}</span>
</li>
))}
@ -304,7 +304,7 @@ export default function StressTestPage() {
<CardTitle className="text-lg">📋 ( 10)</CardTitle>
</CardHeader>
<CardContent>
<div className="h-40 overflow-y-auto rounded bg-black p-4 font-mono text-xs text-green-400">
<div className="h-40 overflow-y-auto rounded bg-black p-4 font-mono text-xs text-emerald-400">
{testLogs.slice(-10).map((log, index) => (
<div key={index}>{log}</div>
))}

View File

@ -66,10 +66,10 @@ export const GlobalFileViewer: React.FC<GlobalFileViewerProps> = ({
return <Video {...iconProps} className="text-purple-600" />;
}
if (['mp3', 'wav', 'flac', 'aac', 'ogg'].includes(extension)) {
return <Music {...iconProps} className="text-green-600" />;
return <Music {...iconProps} className="text-emerald-600" />;
}
if (['zip', 'rar', '7z', 'tar', 'gz'].includes(extension)) {
return <Archive {...iconProps} className="text-yellow-600" />;
return <Archive {...iconProps} className="text-amber-600" />;
}
if (['txt', 'md', 'doc', 'docx', 'pdf', 'rtf'].includes(extension)) {
return <FileText {...iconProps} className="text-destructive" />;
@ -192,7 +192,7 @@ export const GlobalFileViewer: React.FC<GlobalFileViewerProps> = ({
{showControls && (
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground/70" />
<Input
placeholder="파일명으로 검색..."
value={searchQuery}
@ -219,7 +219,7 @@ export const GlobalFileViewer: React.FC<GlobalFileViewerProps> = ({
style={{ maxHeight }}
>
{filteredFiles.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<div className="text-center py-8 text-muted-foreground">
{searchQuery ? "검색 결과가 없습니다." : "저장된 파일이 없습니다."}
</div>
) : (
@ -232,7 +232,7 @@ export const GlobalFileViewer: React.FC<GlobalFileViewerProps> = ({
<div className="font-medium truncate">
{file.realFileName || file.savedFileName}
</div>
<div className="text-sm text-gray-500 flex items-center gap-2">
<div className="text-sm text-muted-foreground flex items-center gap-2">
<span>{formatFileSize(file.fileSize)}</span>
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
@ -273,7 +273,7 @@ export const GlobalFileViewer: React.FC<GlobalFileViewerProps> = ({
variant="ghost"
size="sm"
onClick={() => handleRemove(file)}
className="flex items-center gap-1 text-destructive hover:text-red-700"
className="flex items-center gap-1 text-destructive hover:text-destructive"
>
<Trash2 className="w-3 h-3" />
</Button>

View File

@ -225,7 +225,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="columnName">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Input
id="columnName"
@ -233,7 +233,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol
onChange={(e) => updateColumn({ name: e.target.value })}
placeholder="column_name"
disabled={loading}
className={validationErrors.some((e) => e.includes("컬럼명")) ? "border-red-300" : ""}
className={validationErrors.some((e) => e.includes("컬럼명")) ? "border-destructive/30" : ""}
/>
<p className="text-muted-foreground text-xs"> , // </p>
</div>
@ -255,7 +255,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label>
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Select value={column.inputType} onValueChange={handleInputTypeChange} disabled={loading}>
<SelectTrigger>
@ -354,7 +354,7 @@ export function AddColumnModal({ isOpen, onClose, tableName, onSuccess }: AddCol
<Button
onClick={handleAddColumn}
disabled={!isFormValid || loading}
className="bg-blue-600 hover:bg-blue-700"
className="bg-primary hover:bg-primary/90"
>
{loading ? (
<>

View File

@ -49,7 +49,7 @@ export function AuthenticationConfig({
{/* 인증 타입별 설정 필드 */}
{authType === "api-key" && (
<div className="space-y-4 rounded-md border bg-gray-50 p-4">
<div className="space-y-4 rounded-md border bg-muted p-4">
<h4 className="text-sm font-medium">API Key </h4>
{/* 키 위치 */}
@ -96,7 +96,7 @@ export function AuthenticationConfig({
)}
{authType === "bearer" && (
<div className="space-y-4 rounded-md border bg-gray-50 p-4">
<div className="space-y-4 rounded-md border bg-muted p-4">
<h4 className="text-sm font-medium">Bearer Token </h4>
{/* 토큰 */}
@ -111,14 +111,14 @@ export function AuthenticationConfig({
/>
</div>
<p className="text-xs text-gray-500">
<p className="text-xs text-muted-foreground">
* Authorization &quot;Bearer &#123;token&#125;&quot; .
</p>
</div>
)}
{authType === "basic" && (
<div className="space-y-4 rounded-md border bg-gray-50 p-4">
<div className="space-y-4 rounded-md border bg-muted p-4">
<h4 className="text-sm font-medium">Basic Auth </h4>
{/* 사용자명 */}
@ -145,12 +145,12 @@ export function AuthenticationConfig({
/>
</div>
<p className="text-xs text-gray-500">* Authorization Base64 .</p>
<p className="text-xs text-muted-foreground">* Authorization Base64 .</p>
</div>
)}
{authType === "oauth2" && (
<div className="space-y-4 rounded-md border bg-gray-50 p-4">
<div className="space-y-4 rounded-md border bg-muted p-4">
<h4 className="text-sm font-medium">OAuth 2.0 </h4>
{/* Client ID */}
@ -189,12 +189,12 @@ export function AuthenticationConfig({
/>
</div>
<p className="text-xs text-gray-500">* OAuth 2.0 Client Credentials Grant .</p>
<p className="text-xs text-muted-foreground">* OAuth 2.0 Client Credentials Grant .</p>
</div>
)}
{authType === "db-token" && (
<div className="space-y-4 rounded-md border bg-gray-50 p-4">
<div className="space-y-4 rounded-md border bg-muted p-4">
<h4 className="text-sm font-medium">DB </h4>
<div className="space-y-2">
@ -275,14 +275,14 @@ export function AuthenticationConfig({
/>
</div>
<p className="text-xs text-gray-500">
<p className="text-xs text-muted-foreground">
company_code는 .
</p>
</div>
)}
{authType === "none" && (
<div className="rounded-md border border-dashed p-4 text-center text-sm text-gray-500">
<div className="rounded-md border border-dashed p-4 text-center text-sm text-muted-foreground">
API입니다.
</div>
)}

View File

@ -341,14 +341,14 @@ export function CreateTableModal({
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="tableName">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Input
id="tableName"
value={tableName}
onChange={(e) => handleTableNameChange(e.target.value)}
placeholder="예: customer_info"
className={tableNameError ? "border-red-300" : ""}
className={tableNameError ? "border-destructive/30" : ""}
/>
{tableNameError && <p className="text-destructive text-sm">{tableNameError}</p>}
<p className="text-muted-foreground text-xs"> , // </p>
@ -369,7 +369,7 @@ export function CreateTableModal({
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label>
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Button type="button" variant="outline" size="sm" onClick={addColumn} disabled={loading}>
<Plus className="mr-1 h-4 w-4" />
@ -440,7 +440,7 @@ export function CreateTableModal({
<div className="font-medium">:</div>
<ul className="list-inside list-disc space-y-1">
{validationResult.warnings.map((warning: string, index: number) => (
<li key={index} className="text-sm text-orange-600">
<li key={index} className="text-sm text-amber-600">
{warning}
</li>
))}
@ -471,7 +471,7 @@ export function CreateTableModal({
<Button
onClick={handleCreateTable}
disabled={!isFormValid || loading || (validationResult && !validationResult.isValid)}
className="bg-green-600 hover:bg-green-700"
className="bg-emerald-600 hover:bg-green-700"
>
{loading ? (
<>

View File

@ -276,11 +276,11 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
<TableCell>
<div className="flex items-center gap-2">
{log.success ? (
<CheckCircle2 className="h-4 w-4 text-green-600" />
<CheckCircle2 className="h-4 w-4 text-emerald-600" />
) : (
<XCircle className="h-4 w-4 text-destructive" />
)}
<span className={log.success ? "text-green-600" : "text-destructive"}>
<span className={log.success ? "text-emerald-600" : "text-destructive"}>
{log.success ? "성공" : "실패"}
</span>
</div>
@ -323,7 +323,7 @@ export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) {
<CardTitle className="text-sm font-medium"></CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">{statistics.successfulExecutions}</div>
<div className="text-2xl font-bold text-emerald-600">{statistics.successfulExecutions}</div>
</CardContent>
</Card>

View File

@ -484,7 +484,7 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
<div
className={`rounded-md border p-3 text-sm ${
testResult.success
? "border-green-200 bg-green-50 text-green-800"
? "border-emerald-200 bg-emerald-50 text-emerald-800"
: "border-destructive/20 bg-destructive/10 text-red-800"
}`}
>
@ -527,7 +527,7 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
</Button>
{showAdvanced && (
<div className="space-y-4 border-l-2 border-gray-200 pl-6">
<div className="space-y-4 border-l-2 border-border pl-6">
<div className="grid grid-cols-3 gap-4">
<div>
<Label htmlFor="connection_timeout"> ()</Label>

View File

@ -116,7 +116,7 @@ export function HeadersManager({ headers, onChange }: HeadersManagerProps) {
variant="ghost"
size="sm"
onClick={() => removeHeader(index)}
className="h-8 w-8 p-0 text-red-600 hover:bg-red-50 hover:text-red-700"
className="h-8 w-8 p-0 text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 className="h-4 w-4" />
</Button>
@ -127,12 +127,12 @@ export function HeadersManager({ headers, onChange }: HeadersManagerProps) {
</Table>
</div>
) : (
<div className="rounded-md border border-dashed p-4 text-center text-sm text-gray-500">
<div className="rounded-md border border-dashed p-4 text-center text-sm text-muted-foreground">
. .
</div>
)}
<p className="text-xs text-gray-500">
<p className="text-xs text-muted-foreground">
* HTTP . .
</p>
</div>

View File

@ -239,30 +239,30 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<div className="mb-6 flex items-center justify-center">
<div className="flex items-center gap-4">
<div
className={`flex items-center gap-2 ${step === "basic" ? "text-primary" : 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-emerald-600" : "text-muted-foreground/70"}`}
>
<div
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"}`}
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-emerald-100 text-emerald-600" : "bg-muted"}`}
>
1
</div>
<span className="text-sm font-medium"> </span>
</div>
<div className="h-px w-8 bg-gray-300" />
<div className="h-px w-8 bg-muted/60" />
<div
className={`flex items-center gap-2 ${step === "template" ? "text-primary" : step === "advanced" ? "text-green-600" : "text-gray-400"}`}
className={`flex items-center gap-2 ${step === "template" ? "text-primary" : step === "advanced" ? "text-emerald-600" : "text-muted-foreground/70"}`}
>
<div
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"}`}
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-emerald-100 text-emerald-600" : "bg-muted"}`}
>
2
</div>
<span className="text-sm font-medium">릿 </span>
</div>
<div className="h-px w-8 bg-gray-300" />
<div className={`flex items-center gap-2 ${step === "advanced" ? "text-primary" : "text-gray-400"}`}>
<div className="h-px w-8 bg-muted/60" />
<div className={`flex items-center gap-2 ${step === "advanced" ? "text-primary" : "text-muted-foreground/70"}`}>
<div
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"}`}
className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium ${step === "advanced" ? "bg-primary/20 text-primary" : "bg-muted"}`}
>
3
</div>
@ -305,7 +305,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<Card
key={category.id}
className={`cursor-pointer transition-all ${
formData.category === category.id ? "bg-accent ring-2 ring-blue-500" : "hover:bg-gray-50"
formData.category === category.id ? "bg-accent ring-2 ring-ring" : "hover:bg-muted"
}`}
onClick={() => setFormData((prev) => ({ ...prev, category: category.id }))}
>
@ -314,7 +314,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<IconComponent className="h-5 w-5 text-muted-foreground" />
<div>
<div className="font-medium">{category.name}</div>
<div className="text-xs text-gray-500">{category.description}</div>
<div className="text-xs text-muted-foreground">{category.description}</div>
</div>
</div>
</CardContent>
@ -341,13 +341,13 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<div className="space-y-4">
<div>
<Label> 릿 *</Label>
<p className="mb-3 text-sm text-gray-500"> </p>
<p className="mb-3 text-sm text-muted-foreground"> </p>
<div className="grid grid-cols-2 gap-3">
{LAYOUT_TEMPLATES.map((template) => (
<Card
key={template.id}
className={`cursor-pointer transition-all ${
formData.template === template.id ? "bg-accent ring-2 ring-blue-500" : "hover:bg-gray-50"
formData.template === template.id ? "bg-accent ring-2 ring-ring" : "hover:bg-muted"
}`}
onClick={() =>
setFormData((prev) => ({
@ -364,8 +364,8 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<Badge variant="secondary">{template.zones} </Badge>
</div>
<div className="text-sm text-muted-foreground">{template.description}</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="text-xs text-muted-foreground">: {template.example}</div>
<div className="rounded bg-muted p-2 text-center font-mono text-xs">{template.icon}</div>
</div>
</CardContent>
</Card>
@ -418,7 +418,7 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
>
<Plus className="h-4 w-4" />
</Button>
<span className="text-sm text-gray-500"> </span>
<span className="text-sm text-muted-foreground"> </span>
</div>
</div>
</div>
@ -428,17 +428,17 @@ export const LayoutFormModal: React.FC<LayoutFormModalProps> = ({ open, onOpenCh
<div className="space-y-4">
{generationResult ? (
<Alert
className={generationResult.success ? "border-green-200 bg-green-50" : "border-destructive/20 bg-destructive/10"}
className={generationResult.success ? "border-emerald-200 bg-emerald-50" : "border-destructive/20 bg-destructive/10"}
>
<Info className="h-4 w-4" />
<AlertDescription className={generationResult.success ? "text-green-800" : "text-red-800"}>
<AlertDescription className={generationResult.success ? "text-emerald-800" : "text-red-800"}>
{generationResult.message}
{generationResult.success && generationResult.files && (
<div className="mt-2">
<div className="text-sm font-medium"> :</div>
<ul className="mt-1 space-y-1 text-xs">
{generationResult.files.map((file, index) => (
<li key={index} className="text-green-700">
<li key={index} className="text-emerald-700">
{file}
</li>
))}

View File

@ -748,7 +748,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
</SelectContent>
</Select>
{!isEdit && level !== 1 && (
<p className="text-xs text-gray-500">{getText(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SUBMENU_NOTE)}</p>
<p className="text-xs text-muted-foreground">{getText(MENU_MANAGEMENT_KEYS.FORM_COMPANY_SUBMENU_NOTE)}</p>
)}
</div>
@ -817,7 +817,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
}}
>
<div className="font-medium">{key.langKey}</div>
{key.description && <div className="text-xs text-gray-500">{key.description}</div>}
{key.description && <div className="text-xs text-muted-foreground">{key.description}</div>}
</div>
))}
</div>
@ -825,7 +825,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
)}
</div>
{selectedLangKeyInfo && (
<p className="text-xs text-gray-500">
<p className="text-xs text-muted-foreground">
{getText(MENU_MANAGEMENT_KEYS.FORM_LANG_KEY_SELECTED)
.replace("{key}", selectedLangKeyInfo.langKey)
.replace("{description}", selectedLangKeyInfo.description)}
@ -896,7 +896,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
{/* 검색 입력 */}
<div className="sticky top-0 border-b bg-white p-2">
<div className="relative">
<Search className="absolute top-1/2 left-2 h-4 w-4 -translate-y-1/2 text-gray-400" />
<Search className="absolute top-1/2 left-2 h-4 w-4 -translate-y-1/2 text-muted-foreground/70" />
<Input
placeholder="화면 검색..."
value={screenSearchText}
@ -918,14 +918,14 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
<div
key={`screen-${screen.screenId || screen.id || index}-${screen.screenCode || index}`}
onClick={() => handleScreenSelect(screen)}
className="cursor-pointer border-b px-3 py-2 last:border-b-0 hover:bg-gray-100"
className="cursor-pointer border-b px-3 py-2 last:border-b-0 hover:bg-muted"
>
<div className="flex items-center justify-between">
<div>
<div className="text-sm font-medium">{screen.screenName}</div>
<div className="text-xs text-gray-500">{screen.screenCode}</div>
<div className="text-xs text-muted-foreground">{screen.screenCode}</div>
</div>
<div className="text-xs text-gray-400">ID: {screen.screenId || screen.id || "N/A"}</div>
<div className="text-xs text-muted-foreground/70">ID: {screen.screenId || screen.id || "N/A"}</div>
</div>
</div>
))}
@ -933,7 +933,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
(screen) =>
screen.screenName.toLowerCase().includes(screenSearchText.toLowerCase()) ||
screen.screenCode.toLowerCase().includes(screenSearchText.toLowerCase()),
).length === 0 && <div className="px-3 py-2 text-sm text-gray-500"> .</div>}
).length === 0 && <div className="px-3 py-2 text-sm text-muted-foreground"> .</div>}
</div>
</div>
)}
@ -942,7 +942,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
{/* 선택된 화면 정보 표시 */}
{selectedScreen && (
<div className="bg-accent rounded-md border p-3">
<div className="text-sm font-medium text-blue-900">{selectedScreen.screenName}</div>
<div className="text-sm font-medium text-primary">{selectedScreen.screenName}</div>
<div className="text-primary text-xs">: {selectedScreen.screenCode}</div>
<div className="text-primary text-xs"> URL: {formData.menuUrl}</div>
</div>
@ -971,7 +971,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
{/* 검색창 */}
<div className="border-b p-2">
<div className="relative">
<Search className="absolute top-2.5 left-2 h-4 w-4 text-gray-400" />
<Search className="absolute top-2.5 left-2 h-4 w-4 text-muted-foreground/70" />
<Input
type="text"
placeholder="대시보드 검색..."
@ -995,13 +995,13 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
<div
key={dashboard.id}
onClick={() => handleDashboardSelect(dashboard)}
className="cursor-pointer border-b px-3 py-2 last:border-b-0 hover:bg-gray-100"
className="cursor-pointer border-b px-3 py-2 last:border-b-0 hover:bg-muted"
>
<div className="flex items-center justify-between">
<div>
<div className="text-sm font-medium">{dashboard.title}</div>
{dashboard.description && (
<div className="text-xs text-gray-500">{dashboard.description}</div>
<div className="text-xs text-muted-foreground">{dashboard.description}</div>
)}
</div>
</div>
@ -1012,7 +1012,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
dashboard.title.toLowerCase().includes(dashboardSearchText.toLowerCase()) ||
(dashboard.description &&
dashboard.description.toLowerCase().includes(dashboardSearchText.toLowerCase())),
).length === 0 && <div className="px-3 py-2 text-sm text-gray-500"> .</div>}
).length === 0 && <div className="px-3 py-2 text-sm text-muted-foreground"> .</div>}
</div>
</div>
)}
@ -1021,7 +1021,7 @@ export const MenuFormModal: React.FC<MenuFormModalProps> = ({
{/* 선택된 대시보드 정보 표시 */}
{selectedDashboard && (
<div className="bg-accent rounded-md border p-3">
<div className="text-sm font-medium text-blue-900">{selectedDashboard.title}</div>
<div className="text-sm font-medium text-primary">{selectedDashboard.title}</div>
{selectedDashboard.description && (
<div className="text-primary text-xs">: {selectedDashboard.description}</div>
)}

View File

@ -745,7 +745,7 @@ export function MenuPermissionsTable({ permissions, onPermissionsChange, roleGro
<span> (C)</span>
<Checkbox
onCheckedChange={(checked) => handleSelectAll("createYn", checked as boolean)}
className="data-[state=checked]:bg-green-600"
className="data-[state=checked]:bg-emerald-600"
/>
</div>
</TableHead>
@ -754,7 +754,7 @@ export function MenuPermissionsTable({ permissions, onPermissionsChange, roleGro
<span> (R)</span>
<Checkbox
onCheckedChange={(checked) => handleSelectAll("readYn", checked as boolean)}
className="data-[state=checked]:bg-blue-600"
className="data-[state=checked]:bg-primary"
/>
</div>
</TableHead>
@ -772,7 +772,7 @@ export function MenuPermissionsTable({ permissions, onPermissionsChange, roleGro
<span> (D)</span>
<Checkbox
onCheckedChange={(checked) => handleSelectAll("deleteYn", checked as boolean)}
className="data-[state=checked]:bg-red-600"
className="data-[state=checked]:bg-destructive"
/>
</div>
</TableHead>
@ -834,7 +834,7 @@ export function MenuPermissionsTable({ permissions, onPermissionsChange, roleGro
<Checkbox
checked={menu.createYn === "Y"}
onCheckedChange={(checked) => handlePermissionChange(menu.menuObjid, "createYn", checked as boolean)}
className="data-[state=checked]:bg-green-600"
className="data-[state=checked]:bg-emerald-600"
/>
</div>
<div className="flex items-center justify-between text-sm">
@ -842,7 +842,7 @@ export function MenuPermissionsTable({ permissions, onPermissionsChange, roleGro
<Checkbox
checked={menu.readYn === "Y"}
onCheckedChange={(checked) => handlePermissionChange(menu.menuObjid, "readYn", checked as boolean)}
className="data-[state=checked]:bg-blue-600"
className="data-[state=checked]:bg-primary"
/>
</div>
<div className="flex items-center justify-between text-sm">
@ -858,7 +858,7 @@ export function MenuPermissionsTable({ permissions, onPermissionsChange, roleGro
<Checkbox
checked={menu.deleteYn === "Y"}
onCheckedChange={(checked) => handlePermissionChange(menu.menuObjid, "deleteYn", checked as boolean)}
className="data-[state=checked]:bg-red-600"
className="data-[state=checked]:bg-destructive"
/>
</div>
</div>

View File

@ -564,7 +564,7 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
{testResult && (
<div
className={`rounded-md border p-4 ${
testResult.success ? "border-green-200 bg-green-50" : "border-red-200 bg-red-50"
testResult.success ? "border-emerald-200 bg-emerald-50" : "border-destructive/20 bg-destructive/10"
}`}
>
<div className="mb-2 flex items-center justify-between">
@ -572,17 +572,17 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
{testResult.success ? "성공" : "실패"}
</Badge>
{testResult.response_time && (
<span className="text-sm text-gray-600"> : {testResult.response_time}ms</span>
<span className="text-sm text-muted-foreground"> : {testResult.response_time}ms</span>
)}
</div>
<p className="text-sm">{testResult.message}</p>
{testResult.status_code && (
<p className="mt-1 text-xs text-gray-500"> : {testResult.status_code}</p>
<p className="mt-1 text-xs text-muted-foreground"> : {testResult.status_code}</p>
)}
{testResult.error_details && <p className="mt-2 text-xs text-red-600">{testResult.error_details}</p>}
{testResult.error_details && <p className="mt-2 text-xs text-destructive">{testResult.error_details}</p>}
</div>
)}
</div>

View File

@ -79,9 +79,9 @@ export function RoleDeleteModal({ isOpen, onClose, onSuccess, role }: RoleDelete
<div className="space-y-4">
{/* 경고 메시지 */}
<div className="rounded-lg border border-orange-300 bg-orange-50 p-4">
<div className="rounded-lg border border-orange-300 bg-amber-50 p-4">
<div className="flex items-start gap-3">
<AlertTriangle className="mt-0.5 h-5 w-5 flex-shrink-0 text-orange-600" />
<AlertTriangle className="mt-0.5 h-5 w-5 flex-shrink-0 text-amber-600" />
<div className="space-y-2">
<p className="text-sm font-semibold text-orange-900"> ?</p>
<p className="text-xs text-orange-800">
@ -124,7 +124,7 @@ export function RoleDeleteModal({ isOpen, onClose, onSuccess, role }: RoleDelete
<div
className={`rounded-lg border p-3 text-sm ${
alertType === "success"
? "border-green-300 bg-green-50 text-green-800"
? "border-green-300 bg-emerald-50 text-emerald-800"
: "border-destructive/50 bg-destructive/10 text-destructive"
}`}
>

View File

@ -194,7 +194,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
{/* 권한 그룹명 */}
<div>
<Label htmlFor="authName" className="text-xs sm:text-sm">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Input
id="authName"
@ -210,7 +210,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
{/* 권한 코드 */}
<div>
<Label htmlFor="authCode" className="text-xs sm:text-sm">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Input
id="authCode"
@ -243,7 +243,7 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
) : (
<div>
<Label htmlFor="companyCode" className="text-xs sm:text-sm">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
{isSuperAdmin ? (
<>
@ -345,10 +345,10 @@ export function RoleFormModal({ isOpen, onClose, onSuccess, editingRole }: RoleF
<div
className={`rounded-lg border p-3 text-sm ${
alertType === "success"
? "border-green-300 bg-green-50 text-green-800"
? "border-green-300 bg-emerald-50 text-emerald-800"
: alertType === "error"
? "border-destructive/50 bg-destructive/10 text-destructive"
: "border-blue-300 bg-blue-50 text-blue-800"
: "border-primary/40 bg-primary/10 text-primary"
}`}
>
<div className="flex items-start gap-2">

View File

@ -223,7 +223,7 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
</div>
{selectedMenu && (
<div className="rounded-lg border bg-gray-50 p-4">
<div className="rounded-lg border bg-muted p-4">
<div className="flex items-center justify-between">
<div>
<h3 className="font-medium">
@ -267,7 +267,7 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
<Monitor className="h-5 w-5" />
({assignedScreens.length})
</CardTitle>
<Button onClick={openAssignDialog} className="bg-blue-600 hover:bg-blue-700">
<Button onClick={openAssignDialog} className="bg-primary hover:bg-primary/90">
<Plus className="mr-2 h-4 w-4" />
</Button>
@ -275,15 +275,15 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
</CardHeader>
<CardContent>
{loading ? (
<div className="py-8 text-center text-gray-500"> ...</div>
<div className="py-8 text-center text-muted-foreground"> ...</div>
) : assignedScreens.length === 0 ? (
<div className="py-8 text-center text-gray-500"> . .</div>
<div className="py-8 text-center text-muted-foreground"> . .</div>
) : (
<div className="space-y-3">
{assignedScreens.map((screen) => (
<div
key={screen.screenId}
className="flex items-center justify-between rounded-lg border p-4 hover:bg-gray-50"
className="flex items-center justify-between rounded-lg border p-4 hover:bg-muted"
>
<div className="flex-1">
<div className="flex items-center gap-3">
@ -298,7 +298,7 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
<p className="mt-1 text-sm text-muted-foreground">
: {screen.tableName} | : {screen.createdDate.toLocaleDateString()}
</p>
{screen.description && <p className="mt-1 text-sm text-gray-500">{screen.description}</p>}
{screen.description && <p className="mt-1 text-sm text-muted-foreground">{screen.description}</p>}
</div>
<Button
variant="outline"
@ -307,7 +307,7 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
setSelectedScreen(screen);
setShowUnassignDialog(true);
}}
className="text-destructive hover:text-red-700"
className="text-destructive hover:text-destructive"
>
<X className="h-4 w-4" />
</Button>
@ -330,7 +330,7 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
<div className="space-y-4">
{/* 검색 */}
<div className="relative">
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform text-gray-400" />
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform text-muted-foreground/70" />
<Input
placeholder="화면명 또는 코드로 검색..."
value={searchTerm}
@ -342,13 +342,13 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
{/* 화면 목록 */}
<div className="max-h-64 space-y-2 overflow-y-auto">
{filteredAvailableScreens.length === 0 ? (
<div className="py-4 text-center text-gray-500"> .</div>
<div className="py-4 text-center text-muted-foreground"> .</div>
) : (
filteredAvailableScreens.map((screen) => (
<div
key={screen.screenId}
className={`cursor-pointer rounded-lg border p-3 transition-colors ${
selectedScreen?.screenId === screen.screenId ? "border-primary bg-accent" : "hover:bg-gray-50"
selectedScreen?.screenId === screen.screenId ? "border-primary bg-accent" : "hover:bg-muted"
}`}
onClick={() => setSelectedScreen(screen)}
>
@ -385,7 +385,7 @@ export const ScreenAssignmentTab: React.FC<ScreenAssignmentTabProps> = ({ menus
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setSelectedScreen(null)}></AlertDialogCancel>
<AlertDialogAction onClick={handleUnassignScreen} className="bg-red-600 hover:bg-red-700">
<AlertDialogAction onClick={handleUnassignScreen} className="bg-destructive hover:bg-red-700">
</AlertDialogAction>
</AlertDialogFooter>

View File

@ -116,7 +116,7 @@ export function SortableCodeItem({
}}
onPointerDown={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
className="text-muted-foreground hover:text-foreground -ml-1 flex h-5 w-5 items-center justify-center rounded transition-colors hover:bg-gray-100"
className="text-muted-foreground hover:text-foreground -ml-1 flex h-5 w-5 items-center justify-center rounded transition-colors hover:bg-muted"
title={isExpanded ? "접기" : "펼치기"}
>
{isExpanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
@ -135,12 +135,12 @@ export function SortableCodeItem({
</Badge>
)}
{depth === 2 && (
<Badge variant="outline" className="bg-blue-50 px-1.5 py-0 text-[10px] text-blue-600">
<Badge variant="outline" className="bg-primary/10 px-1.5 py-0 text-[10px] text-primary">
</Badge>
)}
{depth === 3 && (
<Badge variant="outline" className="bg-green-50 px-1.5 py-0 text-[10px] text-green-600">
<Badge variant="outline" className="bg-emerald-50 px-1.5 py-0 text-[10px] text-emerald-600">
</Badge>
)}
@ -196,7 +196,7 @@ export function SortableCodeItem({
onAddChild();
}}
title="하위 코드 추가"
className="text-blue-600 hover:bg-blue-50 hover:text-blue-700"
className="text-primary hover:bg-primary/10 hover:text-primary"
>
<Plus className="h-3 w-3" />
</Button>

View File

@ -102,11 +102,11 @@ export function TableLogViewer({ tableName, open, onOpenChange }: TableLogViewer
const getOperationBadge = (type: string) => {
switch (type) {
case "INSERT":
return <Badge className="bg-green-500"></Badge>;
return <Badge className="bg-emerald-500"></Badge>;
case "UPDATE":
return <Badge className="bg-blue-500"></Badge>;
return <Badge className="bg-primary"></Badge>;
case "DELETE":
return <Badge className="bg-red-500"></Badge>;
return <Badge className="bg-destructive"></Badge>;
default:
return <Badge>{type}</Badge>;
}
@ -150,7 +150,7 @@ export function TableLogViewer({ tableName, open, onOpenChange }: TableLogViewer
<div className="grid grid-cols-2 gap-3 md:grid-cols-3">
<div>
<label className="mb-1 block text-sm text-gray-600"> </label>
<label className="mb-1 block text-sm text-muted-foreground"> </label>
{/* Radix UI Select v2.x: 빈 문자열 value="" 금지 → "__all__" 사용 */}
<Select
value={operationType || "__all__"}
@ -169,22 +169,22 @@ export function TableLogViewer({ tableName, open, onOpenChange }: TableLogViewer
</div>
<div>
<label className="mb-1 block text-sm text-gray-600"> </label>
<label className="mb-1 block text-sm text-muted-foreground"> </label>
<Input type="date" value={startDate} onChange={(e) => setStartDate(e.target.value)} />
</div>
<div>
<label className="mb-1 block text-sm text-gray-600"> </label>
<label className="mb-1 block text-sm text-muted-foreground"> </label>
<Input type="date" value={endDate} onChange={(e) => setEndDate(e.target.value)} />
</div>
<div>
<label className="mb-1 block text-sm text-gray-600"></label>
<label className="mb-1 block text-sm text-muted-foreground"></label>
<Input placeholder="사용자 ID" value={changedBy} onChange={(e) => setChangedBy(e.target.value)} />
</div>
<div>
<label className="mb-1 block text-sm text-gray-600"> ID</label>
<label className="mb-1 block text-sm text-muted-foreground"> ID</label>
<Input placeholder="레코드 ID" value={originalId} onChange={(e) => setOriginalId(e.target.value)} />
</div>
@ -204,7 +204,7 @@ export function TableLogViewer({ tableName, open, onOpenChange }: TableLogViewer
<LoadingSpinner />
</div>
) : logs.length === 0 ? (
<div className="flex h-64 items-center justify-center text-gray-500"> .</div>
<div className="flex h-64 items-center justify-center text-muted-foreground"> .</div>
) : (
<Table>
<TableHeader>
@ -243,7 +243,7 @@ export function TableLogViewer({ tableName, open, onOpenChange }: TableLogViewer
{/* 페이지네이션 */}
<div className="flex items-center justify-between border-t pt-4">
<div className="text-sm text-gray-600">
<div className="text-sm text-muted-foreground">
{total} ( {page} / {totalPages})
</div>
<div className="flex gap-2">

View File

@ -157,12 +157,12 @@ export function TemplateImportExport({ onTemplateImported }: TemplateImportExpor
</CardHeader>
<CardContent className="space-y-4">
<div
className="cursor-pointer rounded-lg border-2 border-dashed border-gray-300 p-8 text-center transition-colors hover:border-gray-400"
className="cursor-pointer rounded-lg border-2 border-dashed border-input p-8 text-center transition-colors hover:border-input"
onClick={triggerFileSelect}
>
<Upload className="mx-auto mb-4 h-12 w-12 text-gray-400" />
<p className="mb-2 text-lg font-medium text-gray-900">릿 JSON </p>
<p className="text-sm text-gray-500"> JSON </p>
<Upload className="mx-auto mb-4 h-12 w-12 text-muted-foreground/70" />
<p className="mb-2 text-lg font-medium text-foreground">릿 JSON </p>
<p className="text-sm text-muted-foreground"> JSON </p>
</div>
<input ref={fileInputRef} type="file" accept=".json" onChange={handleFileUpload} className="hidden" />
</CardContent>
@ -196,7 +196,7 @@ export function TemplateImportExport({ onTemplateImported }: TemplateImportExpor
<Card>
<CardHeader>
<CardTitle className="flex items-center text-lg">
<CheckCircle className="mr-2 h-5 w-5 text-green-600" />
<CheckCircle className="mr-2 h-5 w-5 text-emerald-600" />
3. 릿
</CardTitle>
</CardHeader>

View File

@ -54,28 +54,28 @@ export function UserAuthEditModal({ isOpen, onClose, onSuccess, user }: UserAuth
label: "회사 관리자",
description: "자기 회사 데이터 및 사용자 관리 가능",
icon: <Building2 className="h-4 w-4" />,
color: "text-blue-600",
color: "text-primary",
},
{
value: "USER",
label: "일반 사용자",
description: "자기 회사 데이터 조회/수정만 가능",
icon: <User className="h-4 w-4" />,
color: "text-gray-600",
color: "text-muted-foreground",
},
{
value: "GUEST",
label: "게스트",
description: "제한된 조회 권한",
icon: <Users className="h-4 w-4" />,
color: "text-green-600",
color: "text-emerald-600",
},
{
value: "PARTNER",
label: "협력업체",
description: "협력업체 전용 권한",
icon: <Shield className="h-4 w-4" />,
color: "text-orange-600",
color: "text-amber-600",
},
];
@ -158,7 +158,7 @@ export function UserAuthEditModal({ isOpen, onClose, onSuccess, user }: UserAuth
{/* 권한 선택 */}
<div className="space-y-2">
<Label htmlFor="userType" className="text-sm font-medium">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Select value={selectedUserType} onValueChange={setSelectedUserType}>
<SelectTrigger className="h-10">
@ -180,9 +180,9 @@ export function UserAuthEditModal({ isOpen, onClose, onSuccess, user }: UserAuth
{/* SUPER_ADMIN 경고 */}
{showConfirmation && selectedUserType === "SUPER_ADMIN" && (
<div className="rounded-lg border border-orange-300 bg-orange-50 p-4">
<div className="rounded-lg border border-orange-300 bg-amber-50 p-4">
<div className="flex items-start gap-3">
<AlertTriangle className="mt-0.5 h-5 w-5 flex-shrink-0 text-orange-600" />
<AlertTriangle className="mt-0.5 h-5 w-5 flex-shrink-0 text-amber-600" />
<div className="space-y-2">
<p className="text-sm font-semibold text-orange-900"> </p>
<p className="text-xs text-orange-800">

View File

@ -23,7 +23,7 @@ function AlertModal({ isOpen, onClose, title, message, type = "info" }: AlertMod
const getTypeColor = () => {
switch (type) {
case "success":
return "text-green-600";
return "text-emerald-600";
case "error":
return "text-destructive";
default:
@ -446,7 +446,7 @@ export function UserFormModal({ isOpen, onClose, onSuccess, editingUser }: UserF
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="userId" className="text-sm font-medium">
ID <span className="text-red-500">*</span>
ID <span className="text-destructive">*</span>
</Label>
{isEditMode ? (
<Input id="userId" value={formData.userId} disabled className="bg-muted cursor-not-allowed" />
@ -474,7 +474,7 @@ export function UserFormModal({ isOpen, onClose, onSuccess, editingUser }: UserF
{/* 중복확인 결과 메시지 */}
{duplicateCheckMessage && (
<div
className={`mt-1 text-sm ${duplicateCheckType === "success" ? "text-green-600" : "text-destructive"}`}
className={`mt-1 text-sm ${duplicateCheckType === "success" ? "text-emerald-600" : "text-destructive"}`}
>
{duplicateCheckMessage}
</div>
@ -485,7 +485,7 @@ export function UserFormModal({ isOpen, onClose, onSuccess, editingUser }: UserF
<div className="space-y-2">
<Label htmlFor="userName" className="text-sm font-medium">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Input
id="userName"
@ -501,7 +501,7 @@ export function UserFormModal({ isOpen, onClose, onSuccess, editingUser }: UserF
{!isEditMode && (
<div className="space-y-2">
<Label htmlFor="userPassword" className="text-sm font-medium">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<div className="relative">
<Input
@ -532,7 +532,7 @@ export function UserFormModal({ isOpen, onClose, onSuccess, editingUser }: UserF
{/* 회사 선택 */}
<div className="space-y-2">
<Label htmlFor="companyCode" className="text-sm font-medium">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
{isSuperAdmin ? (
<>

View File

@ -136,7 +136,7 @@ export function UserPasswordResetModal({ isOpen, onClose, userId, userName, onSu
<div className="space-y-4" onKeyDown={handleKeyDown}>
{/* 대상 사용자 정보 */}
<div>
<Label className="text-sm font-medium text-gray-700">
<Label className="text-sm font-medium text-foreground">
:{" "}
<span className="font-semibold">
{userName} ({userId})
@ -197,10 +197,10 @@ export function UserPasswordResetModal({ isOpen, onClose, userId, userName, onSu
{/* 비밀번호 일치 여부 표시 */}
{showMismatchError && <p className="text-sm text-destructive"> .</p>}
{isPasswordMatch && <p className="text-sm text-green-600"> .</p>}
{isPasswordMatch && <p className="text-sm text-emerald-600"> .</p>}
</div>
<div className="text-xs text-gray-500">* , , .</div>
<div className="text-xs text-muted-foreground">* , , .</div>
</div>
<div className="mt-6 flex justify-end space-x-2">

View File

@ -59,7 +59,7 @@ export function DriverListView({ drivers, config, isCompact = false }: DriverLis
return (
<div className="h-full w-full overflow-auto">
<table className="min-w-full divide-y divide-gray-200">
<table className="min-w-full divide-y divide-border">
<thead className="sticky top-0 z-10 bg-muted">
<tr>
{visibleColumns.includes("status") && (
@ -96,7 +96,7 @@ export function DriverListView({ drivers, config, isCompact = false }: DriverLis
)}
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-background">
<tbody className="divide-y divide-border bg-background">
{drivers.map((driver) => {
const statusColors = getStatusColor(driver.status);
return (

View File

@ -180,16 +180,16 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
const renderIcon = (icon?: string, color?: string) => {
const colorClass =
color === "blue"
? "text-blue-600"
? "text-primary"
: color === "orange"
? "text-orange-600"
? "text-amber-600"
: color === "green"
? "text-green-600"
? "text-emerald-600"
: color === "red"
? "text-red-600"
? "text-destructive"
: color === "purple"
? "text-purple-600"
: "text-gray-600";
: "text-muted-foreground";
switch (icon) {
case "truck":
@ -209,16 +209,16 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
const renderFieldGroup = (group: FieldGroup, data: Record<string, any>) => {
const colorClass =
group.color === "blue"
? "text-blue-600"
? "text-primary"
: group.color === "orange"
? "text-orange-600"
? "text-amber-600"
: group.color === "green"
? "text-green-600"
? "text-emerald-600"
: group.color === "red"
? "text-red-600"
? "text-destructive"
: group.color === "purple"
? "text-purple-600"
: "text-gray-600";
: "text-muted-foreground";
return (
<div key={group.id} className="rounded-lg border p-4">

View File

@ -1592,11 +1592,11 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi
<div className="bg-muted flex items-center justify-center gap-2 border-b p-4">
<span className="text-muted-foreground text-sm font-medium">:</span>
{[
{ type: "area" as ToolType, label: "영역", icon: Grid3x3, color: "text-blue-500" },
{ type: "location-bed" as ToolType, label: "베드", icon: Package, color: "text-blue-600" },
{ type: "location-stp" as ToolType, label: "정차", icon: Move, color: "text-gray-500" },
// { type: "crane-gantry" as ToolType, label: "겐트리", icon: Combine, color: "text-green-500" },
{ type: "crane-mobile" as ToolType, label: "크레인", icon: Truck, color: "text-yellow-500" },
{ type: "area" as ToolType, label: "영역", icon: Grid3x3, color: "text-primary" },
{ type: "location-bed" as ToolType, label: "베드", icon: Package, color: "text-primary" },
{ type: "location-stp" as ToolType, label: "정차", icon: Move, color: "text-muted-foreground" },
// { type: "crane-gantry" as ToolType, label: "겐트리", icon: Combine, color: "text-emerald-500" },
{ type: "crane-mobile" as ToolType, label: "크레인", icon: Truck, color: "text-amber-500" },
{ type: "rack" as ToolType, label: "랙", icon: Box, color: "text-purple-500" },
].map((tool) => {
const Icon = tool.icon;
@ -1919,7 +1919,7 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi
</div>
</div>
{isLocationPlaced ? (
<Check className="h-4 w-4 text-green-500" />
<Check className="h-4 w-4 text-emerald-500" />
) : locationType === "location-stp" ? (
<ParkingCircle className="text-muted-foreground h-4 w-4" />
) : (
@ -2052,7 +2052,7 @@ export default function DigitalTwinEditor({ layoutId, layoutName, onBack }: Digi
</div>
{/* 중앙: 3D 캔버스 */}
<div className="relative h-full flex-1 bg-gray-100">
<div className="relative h-full flex-1 bg-muted">
{isLoading ? (
<div className="flex h-full items-center justify-center">
<Loader2 className="text-muted-foreground h-8 w-8 animate-spin" />

View File

@ -716,7 +716,7 @@ export default function DigitalTwinViewer({ layoutId }: DigitalTwinViewerProps)
</p>
)}
{obj.materialCount !== undefined && obj.materialCount > 0 && (
<p className="text-xs text-yellow-600">
<p className="text-xs text-amber-600">
: <span className="font-semibold">{obj.materialCount}</span>
</p>
)}
@ -802,7 +802,7 @@ export default function DigitalTwinViewer({ layoutId }: DigitalTwinViewerProps)
</p>
)}
{locationObj.materialCount !== undefined && locationObj.materialCount > 0 && (
<p className="mt-0.5 text-[10px] text-yellow-600">
<p className="mt-0.5 text-[10px] text-amber-600">
: <span className="font-semibold">{locationObj.materialCount}</span>
</p>
)}

View File

@ -1239,7 +1239,7 @@ export default function Yard3DCanvas({
};
return (
<div className="h-full w-full bg-gray-100" onClick={handleCanvasClick}>
<div className="h-full w-full bg-muted" onClick={handleCanvasClick}>
<Canvas
camera={{
position: [50, 30, 50],

View File

@ -15,16 +15,16 @@ export const OBJECT_COLORS: Record<string, string> = {
// Tailwind 색상 클래스 매핑 (아이콘용)
export const OBJECT_COLOR_CLASSES: Record<string, string> = {
area: "text-blue-500",
"location-bed": "text-blue-600",
"location-stp": "text-gray-500",
"location-temp": "text-orange-500",
area: "text-primary",
"location-bed": "text-primary",
"location-stp": "text-muted-foreground",
"location-temp": "text-amber-500",
"location-dest": "text-emerald-500",
"crane-mobile": "text-purple-500",
rack: "text-red-500",
rack: "text-destructive",
};
// 기본 색상
export const DEFAULT_COLOR = "#3b82f6";
export const DEFAULT_COLOR_CLASS = "text-blue-500";
export const DEFAULT_COLOR_CLASS = "text-primary";

View File

@ -71,7 +71,7 @@ function CategoryNode({
<Folder className="h-4 w-4 shrink-0 text-amber-500" />
)
) : (
<Tag className="h-4 w-4 shrink-0 text-blue-500" />
<Tag className="h-4 w-4 shrink-0 text-primary" />
)}
{/* 카테고리 이름 */}

View File

@ -309,8 +309,8 @@ export function KeyGenerateModal({
preview?.exists
? "border-destructive bg-destructive/10"
: preview?.isOverride
? "border-blue-500 bg-blue-500/10"
: "border-green-500 bg-green-500/10"
? "border-primary bg-primary/10"
: "border-emerald-500 bg-emerald-500/10"
)}>
<div className="flex items-center gap-2">
{previewLoading ? (
@ -318,9 +318,9 @@ export function KeyGenerateModal({
) : preview?.exists ? (
<AlertCircle className="h-4 w-4 text-destructive" />
) : preview?.isOverride ? (
<Info className="h-4 w-4 text-blue-500" />
<Info className="h-4 w-4 text-primary" />
) : (
<CheckCircle2 className="h-4 w-4 text-green-500" />
<CheckCircle2 className="h-4 w-4 text-emerald-500" />
)}
<code className="text-xs font-mono sm:text-sm">
{generatedKeyPreview}
@ -332,7 +332,7 @@ export function KeyGenerateModal({
</p>
)}
{preview?.isOverride && !preview?.exists && (
<p className="mt-1 text-xs text-blue-600">
<p className="mt-1 text-xs text-primary">
. .
</p>
)}

View File

@ -9,6 +9,6 @@ export function ErrorMessage({ message }: ErrorMessageProps) {
if (!message) return null;
return (
<div className="my-4 rounded-lg border border-destructive/20 bg-destructive/10 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-destructive">{message}</div>
);
}

View File

@ -61,7 +61,7 @@ function DraggableItem({
return (
<div
ref={drag}
className={`flex cursor-move items-center gap-2 rounded border p-2 text-sm hover:border-blue-500 hover:bg-blue-50 ${
className={`flex cursor-move items-center gap-2 rounded border p-2 text-sm hover:border-primary hover:bg-primary/10 ${
isDragging ? "opacity-50" : ""
}`}
>

View File

@ -51,7 +51,7 @@ export function BarcodeDesignerCanvas() {
}), [widthPx, heightPx, components.length, addComponent, snapValueToGrid]);
return (
<div className="flex flex-1 items-center justify-center overflow-auto bg-gray-100 p-6">
<div className="flex flex-1 items-center justify-center overflow-auto bg-muted p-6">
<div
key={`canvas-${widthMm}-${heightMm}`}
ref={(r) => {

View File

@ -202,7 +202,7 @@ export function BarcodeLabelCanvasComponent({ component }: Props) {
}}
/>
) : (
<div className="flex h-full w-full items-center justify-center bg-gray-100 text-xs text-gray-400">
<div className="flex h-full w-full items-center justify-center bg-muted text-xs text-muted-foreground/70">
</div>
);
@ -244,7 +244,7 @@ export function BarcodeLabelCanvasComponent({ component }: Props) {
{selected && component.type !== "line" && (
<div
data-resize-handle
className="absolute bottom-0 right-0 h-2 w-2 cursor-se-resize bg-blue-500"
className="absolute bottom-0 right-0 h-2 w-2 cursor-se-resize bg-primary"
onMouseDown={(e) => {
e.stopPropagation();
setIsResizing(true);

View File

@ -151,7 +151,7 @@ export function BarcodePrintPreviewModal({
</p>
{/* 미리보기 캔버스 (축소) */}
<div className="flex justify-center rounded border bg-gray-100 p-4">
<div className="flex justify-center rounded border bg-muted p-4">
<div
className="relative bg-white shadow"
style={{

View File

@ -27,26 +27,26 @@ interface AlertModalProps {
const alertConfig = {
success: {
icon: CheckCircle,
iconColor: "text-green-500",
titleColor: "text-green-700",
iconColor: "text-emerald-500",
titleColor: "text-emerald-700",
buttonVariant: "default" as const,
},
error: {
icon: XCircle,
iconColor: "text-red-500",
titleColor: "text-red-700",
iconColor: "text-destructive",
titleColor: "text-destructive",
buttonVariant: "destructive" as const,
},
warning: {
icon: AlertTriangle,
iconColor: "text-yellow-500",
iconColor: "text-amber-500",
titleColor: "text-yellow-700",
buttonVariant: "default" as const,
},
info: {
icon: Info,
iconColor: "text-blue-500",
titleColor: "text-blue-700",
iconColor: "text-primary",
titleColor: "text-primary",
buttonVariant: "default" as const,
},
};

View File

@ -1131,7 +1131,7 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
<FileSpreadsheet className="h-5 w-5" />
{isMasterDetail && (
<span className="ml-2 rounded bg-blue-100 px-2 py-0.5 text-xs font-normal text-blue-700">
<span className="ml-2 rounded bg-primary/10 px-2 py-0.5 text-xs font-normal text-primary">
-
</span>
)}
@ -1322,15 +1322,15 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
isDragOver
? "border-primary bg-primary/5"
: file
? "border-green-500 bg-green-50"
? "border-emerald-500 bg-emerald-50"
: "border-muted-foreground/25 hover:border-primary hover:bg-muted/50"
)}
>
{file ? (
<div className="flex items-center gap-3">
<FileSpreadsheet className="h-8 w-8 text-green-600" />
<FileSpreadsheet className="h-8 w-8 text-emerald-600" />
<div>
<p className="text-sm font-medium text-green-700">{file.name}</p>
<p className="text-sm font-medium text-emerald-700">{file.name}</p>
<p className="text-xs text-muted-foreground">
</p>
@ -1561,11 +1561,11 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
{/* 중복 체크 안내 */}
{duplicateCheckCount > 0 ? (
<div className="rounded-md border border-blue-200 bg-blue-50 p-3">
<div className="rounded-md border border-primary/20 bg-primary/10 p-3">
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-2">
<Copy className="mt-0.5 h-4 w-4 text-blue-600" />
<div className="text-[10px] text-blue-700 sm:text-xs">
<Copy className="mt-0.5 h-4 w-4 text-primary" />
<div className="text-[10px] text-primary sm:text-xs">
<p className="font-medium">
: {columnMappings
.filter((m) => m.checkDuplicate && m.systemColumn)
@ -1581,12 +1581,12 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
</div>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className="text-[10px] text-blue-700 sm:text-xs"> :</span>
<span className="text-[10px] text-primary sm:text-xs"> :</span>
<Select
value={duplicateAction}
onValueChange={(value) => setDuplicateAction(value as "overwrite" | "skip")}
>
<SelectTrigger className="h-7 w-[100px] text-[10px] sm:text-xs border-blue-300 bg-white">
<SelectTrigger className="h-7 w-[100px] text-[10px] sm:text-xs border-primary/40 bg-white">
<SelectValue />
</SelectTrigger>
<SelectContent>
@ -1686,7 +1686,7 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
<span className="font-medium">{mapping.excelColumn}</span> {" "}
{col?.label || mapping.systemColumn}
{mapping.checkDuplicate && (
<span className="ml-2 text-blue-600">
<span className="ml-2 text-primary">
( : {mapping.duplicateAction === "overwrite" ? "덮어쓰기" : "건너뛰기"})
</span>
)}
@ -1701,9 +1701,9 @@ export const ExcelUploadModal: React.FC<ExcelUploadModalProps> = ({
{/* 중복 체크 요약 */}
{duplicateCheckCount > 0 && (
<div className="rounded-md border border-blue-200 bg-blue-50 p-4">
<h3 className="text-sm font-medium text-blue-800 sm:text-base"> </h3>
<div className="mt-2 space-y-1 text-[10px] text-blue-700 sm:text-xs">
<div className="rounded-md border border-primary/20 bg-primary/10 p-4">
<h3 className="text-sm font-medium text-primary sm:text-base"> </h3>
<div className="mt-2 space-y-1 text-[10px] text-primary sm:text-xs">
<p>
<span className="font-medium"> :</span>{" "}
{columnMappings

View File

@ -140,9 +140,9 @@ const CompactValidationIndicator: React.FC<{
<span className="text-destructive">{validationState.errors.length} </span>
)}
{validationState.warnings.length > 0 && (
<span className="ml-2 text-orange-600">{validationState.warnings.length} </span>
<span className="ml-2 text-amber-600">{validationState.warnings.length} </span>
)}
{validationState.isValid && <span className="text-green-600"> </span>}
{validationState.isValid && <span className="text-emerald-600"> </span>}
</div>
<Button size="sm" onClick={onSave} disabled={!canSave || saveState.status === "saving"} className="h-8">
@ -176,7 +176,7 @@ const ValidationStatusBadge: React.FC<{ status: FormValidationState["status"] }>
variant: "default" as const,
icon: CheckCircle,
text: "유효함",
className: "bg-green-500 hover:bg-green-600",
className: "bg-emerald-500 hover:bg-emerald-600",
};
case "invalid":
return {
@ -238,14 +238,14 @@ const ValidationSummary: React.FC<{ validationState: FormValidationState }> = ({
)}
{validationState.warnings.length > 0 && (
<div className="flex items-center gap-1 text-orange-600">
<div className="flex items-center gap-1 text-amber-600">
<AlertTriangle className="h-4 w-4" />
<span>{validationState.warnings.length} </span>
</div>
)}
{validationState.isValid && validationState.errors.length === 0 && validationState.warnings.length === 0 && (
<div className="flex items-center gap-1 text-green-600">
<div className="flex items-center gap-1 text-emerald-600">
<CheckCircle className="h-4 w-4" />
<span> </span>
</div>
@ -307,8 +307,8 @@ const ValidationErrorItem: React.FC<{ error: ValidationError }> = ({ error }) =>
*/
const ValidationWarningItem: React.FC<{ warning: ValidationWarning }> = ({ warning }) => {
return (
<Alert className="border-orange-200 bg-orange-50 py-2">
<AlertTriangle className="h-4 w-4 text-orange-600" />
<Alert className="border-orange-200 bg-amber-50 py-2">
<AlertTriangle className="h-4 w-4 text-amber-600" />
<AlertDescription className="text-sm">
<span className="font-medium">{warning.field}:</span> {warning.message}
{warning.suggestion && <span className="mt-1 block text-xs text-orange-700">💡 {warning.suggestion}</span>}
@ -364,15 +364,15 @@ export const FieldValidationIndicator: React.FC<{
{showIcon && (
<>
{status === "validating" && <Clock className="text-muted-foreground h-3 w-3 animate-spin" />}
{status === "valid" && !error && <CheckCircle className="h-3 w-3 text-green-600" />}
{status === "valid" && !error && <CheckCircle className="h-3 w-3 text-emerald-600" />}
{error && <AlertCircle className="text-destructive h-3 w-3" />}
{warning && !error && <AlertTriangle className="h-3 w-3 text-orange-600" />}
{warning && !error && <AlertTriangle className="h-3 w-3 text-amber-600" />}
</>
)}
{error && <span className="text-destructive">{error.message}</span>}
{warning && !error && <span className="text-orange-600">{warning.message}</span>}
{warning && !error && <span className="text-amber-600">{warning.message}</span>}
</div>
);
};

View File

@ -1028,7 +1028,7 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
{loading ? (
<div className="flex h-full items-center justify-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-primary"></div>
<p className="text-muted-foreground"> ...</p>
</div>
</div>

View File

@ -102,26 +102,26 @@ export function TableHistoryModal({
const getOperationIcon = (type: string) => {
switch (type) {
case "INSERT":
return <Plus className="h-4 w-4 text-green-600" />;
return <Plus className="h-4 w-4 text-emerald-600" />;
case "UPDATE":
return <FileEdit className="h-4 w-4 text-blue-600" />;
return <FileEdit className="h-4 w-4 text-primary" />;
case "DELETE":
return <Trash2 className="h-4 w-4 text-red-600" />;
return <Trash2 className="h-4 w-4 text-destructive" />;
default:
return <Clock className="h-4 w-4 text-gray-600" />;
return <Clock className="h-4 w-4 text-muted-foreground" />;
}
};
const getOperationBadge = (type: string) => {
switch (type) {
case "INSERT":
return <span className="text-sm font-medium text-green-600"></span>;
return <span className="text-sm font-medium text-emerald-600"></span>;
case "UPDATE":
return <span className="text-sm font-medium text-blue-600"></span>;
return <span className="text-sm font-medium text-primary"></span>;
case "DELETE":
return <span className="text-sm font-medium text-red-600"></span>;
return <span className="text-sm font-medium text-destructive"></span>;
default:
return <span className="text-sm font-medium text-gray-600">{type}</span>;
return <span className="text-sm font-medium text-muted-foreground">{type}</span>;
}
};
@ -301,8 +301,8 @@ export function TableHistoryModal({
) : (
<div className="space-y-6">
{timeline.map((event, index) => (
<div key={index} className="relative border-l-2 border-gray-200 pb-6 pl-8 last:border-l-0">
<div className="absolute top-0 -left-3 rounded-full border-2 border-gray-200 bg-white p-1">
<div key={index} className="relative border-l-2 border-border pb-6 pl-8 last:border-l-0">
<div className="absolute top-0 -left-3 rounded-full border-2 border-border bg-white p-1">
{getOperationIcon(event.operation_type)}
</div>
@ -325,12 +325,12 @@ export function TableHistoryModal({
<p className="text-muted-foreground text-xs font-medium"> :</p>
<div className="space-y-1">
{event.changes.map((change, idx) => (
<div key={idx} className="rounded bg-gray-50 p-2 text-xs">
<div key={idx} className="rounded bg-muted p-2 text-xs">
<span className="font-mono font-medium">{change.column}</span>
<div className="mt-1 flex items-center gap-2">
<span className="text-red-600 line-through">{change.oldValue || "(없음)"}</span>
<span className="text-destructive line-through">{change.oldValue || "(없음)"}</span>
<span></span>
<span className="font-medium text-green-600">{change.newValue || "(없음)"}</span>
<span className="font-medium text-emerald-600">{change.newValue || "(없음)"}</span>
</div>
</div>
))}
@ -355,7 +355,7 @@ export function TableHistoryModal({
</div>
) : (
<table className="w-full text-xs">
<thead className="sticky top-0 border-b bg-gray-50">
<thead className="sticky top-0 border-b bg-muted">
<tr>
{!recordId && <th className="p-2 text-left font-medium"></th>}
<th className="p-2 text-left font-medium"></th>
@ -370,11 +370,11 @@ export function TableHistoryModal({
{detailRecords.map((record) => {
const displayValue = getDisplayValue(record);
return (
<tr key={record.log_id} className="border-b hover:bg-gray-50">
<tr key={record.log_id} className="border-b hover:bg-muted">
{!recordId && (
<td className="p-2">
{displayValue ? (
<span className="font-medium text-gray-900">{displayValue}</span>
<span className="font-medium text-foreground">{displayValue}</span>
) : (
<span className="text-muted-foreground text-xs">-</span>
)}
@ -382,8 +382,8 @@ export function TableHistoryModal({
)}
<td className="p-2">{getOperationBadge(record.operation_type)}</td>
<td className="p-2 font-mono">{record.changed_column}</td>
<td className="max-w-[200px] truncate p-2 text-red-600">{record.old_value || "-"}</td>
<td className="max-w-[200px] truncate p-2 text-green-600">{record.new_value || "-"}</td>
<td className="max-w-[200px] truncate p-2 text-destructive">{record.old_value || "-"}</td>
<td className="max-w-[200px] truncate p-2 text-emerald-600">{record.new_value || "-"}</td>
<td className="p-2">{record.changed_by}</td>
<td className="text-muted-foreground p-2">{formatDate(record.changed_at)}</td>
</tr>

View File

@ -10,7 +10,7 @@ interface ValidationMessageProps {
export function ValidationMessage({ message, isValid, isLoading, className }: ValidationMessageProps) {
if (isLoading) {
return <p className={cn("text-sm text-gray-500", className)}> ...</p>;
return <p className={cn("text-sm text-muted-foreground", className)}> ...</p>;
}
if (!message) {
@ -18,6 +18,6 @@ export function ValidationMessage({ message, isValid, isLoading, className }: Va
}
return (
<p className={cn("text-sm transition-colors", isValid ? "text-green-600" : "text-destructive", className)}>{message}</p>
<p className={cn("text-sm transition-colors", isValid ? "text-emerald-600" : "text-destructive", className)}>{message}</p>
);
}

View File

@ -212,16 +212,16 @@ export function ListTestWidget({ element }: ListTestWidgetProps) {
const renderIcon = (icon?: string, color?: string) => {
const colorClass =
color === "blue"
? "text-blue-600"
? "text-primary"
: color === "orange"
? "text-orange-600"
? "text-amber-600"
: color === "green"
? "text-green-600"
? "text-emerald-600"
: color === "red"
? "text-red-600"
? "text-destructive"
: color === "purple"
? "text-purple-600"
: "text-gray-600";
: "text-muted-foreground";
switch (icon) {
case "truck":
@ -241,16 +241,16 @@ export function ListTestWidget({ element }: ListTestWidgetProps) {
const renderFieldGroup = (group: FieldGroup, groupData: Record<string, any>) => {
const colorClass =
group.color === "blue"
? "text-blue-600"
? "text-primary"
: group.color === "orange"
? "text-orange-600"
? "text-amber-600"
: group.color === "green"
? "text-green-600"
? "text-emerald-600"
: group.color === "red"
? "text-red-600"
? "text-destructive"
: group.color === "purple"
? "text-purple-600"
: "text-gray-600";
: "text-muted-foreground";
return (
<div key={group.id} className="rounded-lg border p-4">

View File

@ -1352,8 +1352,8 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
{/* 이동경로 날짜 선택 */}
{selectedUserId && (
<div className="flex items-center gap-1 rounded border bg-blue-50 px-2 py-1">
<span className="text-xs text-blue-600">🛣</span>
<div className="flex items-center gap-1 rounded border bg-primary/10 px-2 py-1">
<span className="text-xs text-primary">🛣</span>
<input
type="date"
value={routeDate}
@ -1363,10 +1363,10 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
loadRoute(selectedUserId, e.target.value);
}
}}
className="h-6 rounded border-none bg-transparent px-1 text-xs text-blue-600 focus:outline-none"
className="h-6 rounded border-none bg-transparent px-1 text-xs text-primary focus:outline-none"
/>
<span className="text-xs text-blue-600">({routePoints.length})</span>
<button onClick={clearRoute} className="ml-1 text-xs text-blue-400 hover:text-blue-600">
<span className="text-xs text-primary">({routePoints.length})</span>
<button onClick={clearRoute} className="ml-1 text-xs text-primary/80 hover:text-primary">
</button>
</div>
@ -1494,15 +1494,15 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
const parsed = JSON.parse(matchingPolygon.description);
popupContent = `
<div class="min-w-[200px]">
${matchingPolygon.source ? `<div class="mb-2 border-b pb-2"><div class="text-gray-500 text-xs">📡 ${matchingPolygon.source}</div></div>` : ""}
<div class="bg-gray-100 rounded p-2">
<div class="text-gray-900 mb-1 text-xs font-semibold"> </div>
${matchingPolygon.source ? `<div class="mb-2 border-b pb-2"><div class="text-muted-foreground text-xs">📡 ${matchingPolygon.source}</div></div>` : ""}
<div class="bg-muted rounded p-2">
<div class="text-foreground mb-1 text-xs font-semibold"> </div>
<div class="space-y-2">
${popupFields
.map((field) => {
const value = parsed[field.fieldName];
if (value === undefined || value === null) return "";
return `<div class="text-xs"><span class="text-gray-600 font-medium">${field.label}:</span> <span class="text-gray-900">${value}</span></div>`;
return `<div class="text-xs"><span class="text-muted-foreground font-medium">${field.label}:</span> <span class="text-foreground">${value}</span></div>`;
})
.join("")}
</div>
@ -1980,7 +1980,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
<button
onClick={() => loadTripInfo(identifier)}
disabled={tripInfoLoading === identifier}
className="w-full rounded bg-gray-100 px-2 py-1.5 text-xs text-gray-700 hover:bg-gray-200 disabled:opacity-50"
className="w-full rounded bg-muted px-2 py-1.5 text-xs text-foreground hover:bg-muted/80 disabled:opacity-50"
>
{tripInfoLoading === identifier ? "로딩 중..." : "📊 운행/공차 정보 보기"}
</button>
@ -1996,10 +1996,10 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
{/* 운행 정보 */}
{hasTripInfo && (
<div className="mb-2">
<div className="text-xs font-semibold text-blue-600 mb-1">🚛 </div>
<div className="bg-blue-50 rounded p-2 space-y-1">
<div className="text-xs font-semibold text-primary mb-1">🚛 </div>
<div className="bg-primary/10 rounded p-2 space-y-1">
{(info.last_trip_start || info.last_trip_end) && (
<div className="text-[10px] text-gray-600">
<div className="text-[10px] text-muted-foreground">
<span className="font-medium">:</span>{" "}
{formatDateTime(info.last_trip_start)} ~ {formatDateTime(info.last_trip_end)}
</div>
@ -2007,20 +2007,20 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
<div className="flex gap-3 text-[10px]">
{info.last_trip_distance !== undefined && info.last_trip_distance !== null && (
<span>
<span className="font-medium text-gray-600">:</span>{" "}
<span className="text-blue-700 font-semibold">{formatDistance(info.last_trip_distance)}</span>
<span className="font-medium text-muted-foreground">:</span>{" "}
<span className="text-primary font-semibold">{formatDistance(info.last_trip_distance)}</span>
</span>
)}
{info.last_trip_time !== undefined && info.last_trip_time !== null && (
<span>
<span className="font-medium text-gray-600">:</span>{" "}
<span className="text-blue-700 font-semibold">{formatTime(info.last_trip_time)}</span>
<span className="font-medium text-muted-foreground">:</span>{" "}
<span className="text-primary font-semibold">{formatTime(info.last_trip_time)}</span>
</span>
)}
</div>
{/* 출발지/도착지 */}
{(info.departure || info.arrival) && (
<div className="text-[10px] text-gray-600 pt-1 border-t border-blue-100">
<div className="text-[10px] text-muted-foreground pt-1 border-t border-primary/10">
{info.departure && <span>: {info.departure}</span>}
{info.departure && info.arrival && " → "}
{info.arrival && <span>: {info.arrival}</span>}
@ -2033,10 +2033,10 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
{/* 공차 정보 */}
{hasEmptyTripInfo && (
<div>
<div className="text-xs font-semibold text-orange-600 mb-1">📦 </div>
<div className="bg-orange-50 rounded p-2 space-y-1">
<div className="text-xs font-semibold text-amber-600 mb-1">📦 </div>
<div className="bg-amber-50 rounded p-2 space-y-1">
{(info.last_empty_start || info.last_empty_end) && (
<div className="text-[10px] text-gray-600">
<div className="text-[10px] text-muted-foreground">
<span className="font-medium">:</span>{" "}
{formatDateTime(info.last_empty_start)} ~ {formatDateTime(info.last_empty_end)}
</div>
@ -2044,13 +2044,13 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
<div className="flex gap-3 text-[10px]">
{info.last_empty_distance !== undefined && info.last_empty_distance !== null && (
<span>
<span className="font-medium text-gray-600">:</span>{" "}
<span className="font-medium text-muted-foreground">:</span>{" "}
<span className="text-orange-700 font-semibold">{formatDistance(info.last_empty_distance)}</span>
</span>
)}
{info.last_empty_time !== undefined && info.last_empty_time !== null && (
<span>
<span className="font-medium text-gray-600">:</span>{" "}
<span className="font-medium text-muted-foreground">:</span>{" "}
<span className="text-orange-700 font-semibold">{formatTime(info.last_empty_time)}</span>
</span>
)}
@ -2085,7 +2085,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
<button
onClick={() => loadRoute(visibleUserId)}
disabled={routeLoading}
className="w-full rounded bg-blue-500 px-2 py-1 text-xs text-white hover:bg-blue-600 disabled:opacity-50"
className="w-full rounded bg-primary px-2 py-1 text-xs text-white hover:bg-primary disabled:opacity-50"
>
{routeLoading && selectedUserId === visibleUserId ? "로딩 중..." : "🛣️ 이동경로 보기"}
</button>

View File

@ -357,7 +357,7 @@ export default function VehicleMapOnlyWidget({ element, refreshInterval = 30000
<button
onClick={() => loadRoute(vehicle)}
disabled={isRouteLoading}
className="w-full rounded bg-blue-500 px-2 py-1 text-xs text-white hover:bg-blue-600 disabled:opacity-50"
className="w-full rounded bg-primary px-2 py-1 text-xs text-white hover:bg-primary disabled:opacity-50"
>
{isRouteLoading && selectedVehicle?.id === vehicle.id
? "로딩 중..."
@ -391,7 +391,7 @@ export default function VehicleMapOnlyWidget({ element, refreshInterval = 30000
{/* 이동경로 정보 표시 - 상단으로 이동하여 주석 처리 */}
{/* {selectedVehicle && routePoints.length > 0 && (
<div className="absolute bottom-2 right-2 z-[1000] rounded-lg bg-blue-500/90 p-2 shadow-lg backdrop-blur-sm">
<div className="absolute bottom-2 right-2 z-[1000] rounded-lg bg-primary/90 p-2 shadow-lg backdrop-blur-sm">
<div className="flex items-center gap-2">
<div className="text-xs text-white">
<div className="font-semibold">🛣 {selectedVehicle.name} </div>

View File

@ -733,13 +733,13 @@ export const ConnectionSetupModal: React.FC<ConnectionSetupModalProps> = ({
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2">
<CheckCircle className="h-5 w-5 text-green-500" />
<CheckCircle className="h-5 w-5 text-emerald-500" />
</AlertDialogTitle>
<AlertDialogDescription className="text-base">
<span className="font-medium text-green-600">{createdConnectionName}</span> .
<span className="font-medium text-emerald-600">{createdConnectionName}</span> .
<br />
<span className="mt-2 block text-sm text-gray-500">
<span className="mt-2 block text-sm text-muted-foreground">
.
</span>
</AlertDialogDescription>

View File

@ -800,7 +800,7 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
]);
return (
<div className="data-flow-designer h-screen bg-gray-100">
<div className="data-flow-designer h-screen bg-muted">
<div className="flex h-full">
{/* 사이드바 */}
<DataFlowSidebar
@ -892,12 +892,12 @@ export const DataFlowDesigner: React.FC<DataFlowDesignerProps> = ({
{/* 안내 메시지 */}
{nodes.length === 0 && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="text-center text-gray-500">
<div className="text-center text-muted-foreground">
<div className="mb-2 text-2xl">📊</div>
<div className="mb-1 text-lg font-medium"> </div>
<div className="text-sm">
<div> </div>
<div className="mt-1 text-xs text-gray-400"> Del </div>
<div className="mt-1 text-xs text-muted-foreground/70"> Del </div>
</div>
</div>
</div>

View File

@ -35,9 +35,9 @@ export const DataFlowSidebar: React.FC<DataFlowSidebarProps> = ({
getSelectedTableNames,
}) => {
return (
<div className="w-80 border-r border-gray-200 bg-white shadow-lg">
<div className="w-80 border-r border-border bg-white shadow-lg">
<div className="p-6">
<h2 className="mb-6 text-xl font-bold text-gray-800"> </h2>
<h2 className="mb-6 text-xl font-bold text-foreground"> </h2>
{/* 테이블 선택기 */}
<TableSelector companyCode={companyCode} onTableAdd={onTableAdd} selectedTables={getSelectedTableNames()} />
@ -46,7 +46,7 @@ export const DataFlowSidebar: React.FC<DataFlowSidebarProps> = ({
<div className="space-y-3">
<button
onClick={onRemoveOrphanedNodes}
className="w-full rounded-lg bg-orange-500 p-3 font-medium text-white transition-colors hover:bg-orange-600"
className="w-full rounded-lg bg-amber-500 p-3 font-medium text-white transition-colors hover:bg-orange-600"
disabled={nodes.length === 0}
>
🧹
@ -54,14 +54,14 @@ export const DataFlowSidebar: React.FC<DataFlowSidebarProps> = ({
<button
onClick={onClearAll}
className="w-full rounded-lg bg-destructive/100 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-destructive"
>
🗑
</button>
<button
onClick={onOpenSaveModal}
className={`w-full rounded-lg bg-green-500 p-3 font-medium text-white transition-colors hover:bg-green-600 ${
className={`w-full rounded-lg bg-emerald-500 p-3 font-medium text-white transition-colors hover:bg-emerald-600 ${
hasUnsavedChanges ? "animate-pulse" : ""
}`}
>
@ -70,8 +70,8 @@ export const DataFlowSidebar: React.FC<DataFlowSidebarProps> = ({
</div>
{/* 통계 정보 */}
<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="mt-6 rounded-lg bg-muted p-4">
<div className="mb-2 text-sm font-semibold text-foreground"></div>
<div className="space-y-1 text-sm text-muted-foreground">
<div className="flex justify-between">
<span> :</span>
@ -83,7 +83,7 @@ export const DataFlowSidebar: React.FC<DataFlowSidebarProps> = ({
</div>
<div className="flex justify-between">
<span> :</span>
<span className="font-medium text-orange-600">{tempRelationships.length}</span>
<span className="font-medium text-amber-600">{tempRelationships.length}</span>
</div>
<div className="flex justify-between">
<span> ID:</span>
@ -98,7 +98,7 @@ export const DataFlowSidebar: React.FC<DataFlowSidebarProps> = ({
</span>
</div>
{hasUnsavedChanges && (
<div className="mt-2 text-xs font-medium text-orange-600"> </div>
<div className="mt-2 text-xs font-medium text-amber-600"> </div>
)}
</div>
</div>

View File

@ -24,7 +24,7 @@ export const EdgeInfoPanel: React.FC<EdgeInfoPanelProps> = ({
return (
<div
className="fixed z-50 rounded-xl border border-gray-200 bg-white shadow-2xl"
className="fixed z-50 rounded-xl border border-border bg-white shadow-2xl"
style={{
left: position.x - 160,
top: position.y - 100,
@ -33,7 +33,7 @@ export const EdgeInfoPanel: React.FC<EdgeInfoPanelProps> = ({
}}
>
{/* 헤더 */}
<div className="flex items-center justify-between rounded-t-xl border-b border-gray-200 bg-blue-600 p-4">
<div className="flex items-center justify-between rounded-t-xl border-b border-border bg-primary p-4">
<div className="flex items-center gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-white/20 backdrop-blur-sm">
<span className="text-sm text-white">🔗</span>
@ -52,10 +52,10 @@ export const EdgeInfoPanel: React.FC<EdgeInfoPanelProps> = ({
</div>
{/* 관계 정보 요약 */}
<div className="border-b border-gray-100 bg-gray-50 p-3">
<div className="border-b border-border bg-muted p-3">
<div className="flex items-center justify-center gap-4">
<div className="text-center">
<div className="text-xs font-medium tracking-wide text-gray-500 uppercase"> </div>
<div className="text-xs font-medium tracking-wide text-muted-foreground uppercase"> </div>
<div className="mt-1 inline-flex items-center rounded-full bg-indigo-100 px-2 py-0.5 text-xs font-semibold text-indigo-800">
{edgeInfo.connectionType}
</div>
@ -68,7 +68,7 @@ export const EdgeInfoPanel: React.FC<EdgeInfoPanelProps> = ({
{/* From 테이블 */}
<div className="rounded-lg border-l-4 border-emerald-400 bg-emerald-50 p-3">
<div className="mb-2 text-xs font-bold tracking-wide text-emerald-700 uppercase">FROM</div>
<div className="mb-2 text-base font-bold text-gray-800">{edgeInfo.fromTable}</div>
<div className="mb-2 text-base font-bold text-foreground">{edgeInfo.fromTable}</div>
<div className="space-y-1">
<div className="flex flex-wrap gap-2">
{edgeInfo.fromColumns.map((column, index) => (
@ -89,15 +89,15 @@ export const EdgeInfoPanel: React.FC<EdgeInfoPanelProps> = ({
</div>
{/* To 테이블 */}
<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-base font-bold text-gray-800">{edgeInfo.toTable}</div>
<div className="rounded-lg border-l-4 border-primary/60 bg-accent p-3">
<div className="mb-2 text-xs font-bold tracking-wide text-primary uppercase">TO</div>
<div className="mb-2 text-base font-bold text-foreground">{edgeInfo.toTable}</div>
<div className="space-y-1">
<div className="flex flex-wrap gap-2">
{edgeInfo.toColumns.map((column, index) => (
<span
key={index}
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"
className="inline-flex items-center rounded-md bg-primary/20 px-2.5 py-0.5 text-xs font-medium text-primary ring-1 ring-primary/20"
>
{column}
</span>
@ -108,16 +108,16 @@ export const EdgeInfoPanel: React.FC<EdgeInfoPanelProps> = ({
</div>
{/* 액션 버튼 */}
<div className="flex gap-2 border-t border-gray-200 bg-gray-50 p-3">
<div className="flex gap-2 border-t border-border bg-muted p-3">
<button
onClick={onEdit}
className="flex flex-1 items-center justify-center gap-1 rounded-lg bg-blue-600 px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all hover:bg-blue-700 hover:shadow-md"
className="flex flex-1 items-center justify-center gap-1 rounded-lg bg-primary px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all hover:bg-primary/90 hover:shadow-md"
>
<span></span>
</button>
<button
onClick={onDelete}
className="flex flex-1 items-center justify-center gap-1 rounded-lg bg-red-600 px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all hover:bg-red-700 hover:shadow-md"
className="flex flex-1 items-center justify-center gap-1 rounded-lg bg-destructive px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all hover:bg-red-700 hover:shadow-md"
>
<span></span>
</button>

View File

@ -136,16 +136,16 @@ export const RelationshipListModal: React.FC<RelationshipListModalProps> = ({
return (
<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-primary/10 bg-gradient-to-r from-primary/5 to-indigo-50 p-3">
<div className="flex items-center gap-2">
<div className="rounded-full bg-primary/20 p-1">
<span className="text-sm text-primary">🔗</span>
</div>
<div className="text-sm font-semibold text-gray-800"> </div>
<div className="text-sm font-semibold text-foreground"> </div>
</div>
<button
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-muted-foreground"
className="flex h-6 w-6 items-center justify-center rounded-full text-muted-foreground/70 transition-colors hover:bg-muted hover:text-muted-foreground"
>
<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" />
@ -159,10 +159,10 @@ export const RelationshipListModal: React.FC<RelationshipListModalProps> = ({
{relationships.map((relationship) => (
<div
key={relationship.id}
className="rounded-lg border border-gray-200 p-3 transition-all hover:border-blue-300 hover:bg-accent"
className="rounded-lg border border-border p-3 transition-all hover:border-primary/40 hover:bg-accent"
>
<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-foreground">
{relationship.relationshipName || `${relationship.fromTable}${relationship.toTable}`}
</h4>
<div className="flex items-center gap-1">
@ -172,7 +172,7 @@ export const RelationshipListModal: React.FC<RelationshipListModalProps> = ({
e.stopPropagation();
handleEdit(relationship);
}}
className="flex h-6 w-6 items-center justify-center rounded text-gray-400 hover:bg-primary/20 hover:text-primary"
className="flex h-6 w-6 items-center justify-center rounded text-muted-foreground/70 hover:bg-primary/20 hover:text-primary"
title="관계 편집"
>
<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();
handleDelete(relationship);
}}
className="flex h-6 w-6 items-center justify-center rounded text-gray-400 hover:bg-destructive/20 hover:text-destructive"
className="flex h-6 w-6 items-center justify-center rounded text-muted-foreground/70 hover:bg-destructive/20 hover:text-destructive"
title="관계 삭제"
>
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">

View File

@ -161,13 +161,13 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
</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-muted p-4">
<div className="text-center">
<div className="text-2xl font-bold text-primary">{relationships.length}</div>
<div className="text-sm text-muted-foreground"> </div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-green-600">{connectedTables.length}</div>
<div className="text-2xl font-bold text-emerald-600">{connectedTables.length}</div>
<div className="text-sm text-muted-foreground"> </div>
</div>
<div className="text-center">
@ -207,7 +207,7 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
{relationships.map((relationship, index) => (
<div
key={relationship.id || index}
className="flex items-center justify-between rounded-lg border bg-white p-3 hover:bg-gray-50"
className="flex items-center justify-between rounded-lg border bg-white p-3 hover:bg-muted"
>
<div className="flex-1">
<div className="flex items-center gap-2 text-sm">
@ -234,10 +234,10 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
{/* 관계가 없는 경우 안내 */}
{relationships.length === 0 && (
<div className="py-8 text-center text-gray-500">
<div className="py-8 text-center text-muted-foreground">
<div className="mb-2 text-4xl">📭</div>
<div className="text-sm"> .</div>
<div className="mt-1 text-xs text-gray-400"> .</div>
<div className="mt-1 text-xs text-muted-foreground/70"> .</div>
</div>
)}
</div>
@ -249,7 +249,7 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
<Button
onClick={handleSave}
disabled={isLoading || relationships.length === 0}
className="bg-blue-600 hover:bg-blue-700"
className="bg-primary hover:bg-primary/90"
>
{isLoading ? (
<div className="flex items-center gap-2">
@ -269,13 +269,13 @@ const SaveDiagramModal: React.FC<SaveDiagramModalProps> = ({
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2">
<CheckCircle className="h-5 w-5 text-green-500" />
<CheckCircle className="h-5 w-5 text-emerald-500" />
</AlertDialogTitle>
<AlertDialogDescription className="text-base">
<span className="font-medium text-green-600">{savedDiagramName}</span> .
<span className="font-medium text-emerald-600">{savedDiagramName}</span> .
<br />
<span className="mt-2 block text-sm text-gray-500">
<span className="mt-2 block text-sm text-muted-foreground">
.
</span>
</AlertDialogDescription>

View File

@ -26,14 +26,14 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
return (
<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-primary/10 bg-gradient-to-r from-primary/5 to-indigo-50 p-3">
<div className="flex items-center gap-2">
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-primary/20">
<span className="text-sm text-primary">📋</span>
</div>
<div>
<div className="text-sm font-semibold text-gray-800"> </div>
<div className="text-xs text-gray-500">
<div className="text-sm font-semibold text-foreground"> </div>
<div className="text-xs text-muted-foreground">
{selectedNodes.length === 1
? "FROM 테이블 선택됨"
: selectedNodes.length === 2
@ -44,7 +44,7 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
</div>
<button
onClick={onClose}
className="flex h-5 w-5 items-center justify-center rounded-full text-gray-400 hover:bg-gray-100 hover:text-muted-foreground"
className="flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground/70 hover:bg-muted hover:text-muted-foreground"
>
</button>
@ -66,14 +66,14 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
index === 0
? "border-l-4 border-emerald-400 bg-emerald-50"
: index === 1
? "border-l-4 border-blue-400 bg-accent"
? "border-l-4 border-primary/60 bg-accent"
: "bg-muted"
}`}
>
<div className="mb-1 flex items-center justify-between">
<div
className={`text-xs font-medium ${
index === 0 ? "text-emerald-700" : index === 1 ? "text-blue-700" : "text-gray-700"
index === 0 ? "text-emerald-700" : index === 1 ? "text-primary" : "text-foreground"
}`}
>
{displayName}
@ -81,7 +81,7 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
{selectedNodes.length === 2 && (
<div
className={`rounded-full px-2 py-0.5 text-xs font-bold ${
index === 0 ? "bg-emerald-200 text-emerald-800" : "bg-blue-200 text-blue-800"
index === 0 ? "bg-emerald-200 text-emerald-800" : "bg-blue-200 text-primary"
}`}
>
{index === 0 ? "FROM" : "TO"}
@ -94,7 +94,7 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
{/* 연결 화살표 (마지막이 아닌 경우) */}
{index < selectedNodes.length - 1 && (
<div className="flex justify-center py-1">
<div className="text-gray-400"></div>
<div className="text-muted-foreground/70"></div>
</div>
)}
</div>
@ -104,14 +104,14 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
</div>
{/* 액션 버튼 */}
<div className="flex gap-2 border-t border-blue-100 p-3">
<div className="flex gap-2 border-t border-primary/10 p-3">
<button
onClick={onOpenConnectionModal}
disabled={!canCreateConnection}
className={`flex flex-1 items-center justify-center gap-1 rounded-lg px-3 py-2 text-xs font-medium transition-colors ${
canCreateConnection
? "bg-accent0 text-white hover:bg-blue-600"
: "cursor-not-allowed bg-gray-300 text-gray-500"
? "bg-accent0 text-white hover:bg-primary"
: "cursor-not-allowed bg-muted/60 text-muted-foreground"
}`}
>
<span>🔗</span>
@ -119,7 +119,7 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
</button>
<button
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-muted-foreground hover:bg-gray-300"
className="flex flex-1 items-center justify-center gap-1 rounded-lg bg-muted/80 px-3 py-2 text-xs font-medium text-muted-foreground hover:bg-muted/60"
>
<span>🗑</span>
<span></span>

View File

@ -32,13 +32,13 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
const { table, onColumnClick, onScrollAreaEnter, onScrollAreaLeave, selectedColumns = [] } = data;
return (
<div className="relative flex min-w-[280px] cursor-pointer flex-col overflow-hidden rounded-lg border-2 border-gray-300 bg-white shadow-lg transition-all hover:shadow-xl">
<div className="relative flex min-w-[280px] cursor-pointer flex-col overflow-hidden rounded-lg border-2 border-input bg-white shadow-lg transition-all hover:shadow-xl">
{/* React Flow Handles - 숨김 처리 */}
<Handle type="target" position={Position.Left} id="left" className="!invisible !h-1 !w-1" />
<Handle type="source" position={Position.Right} id="right" className="!invisible !h-1 !w-1" />
{/* 테이블 헤더 - 통일된 디자인 */}
<div className="bg-blue-600 p-3 text-white">
<div className="bg-primary p-3 text-white">
<h3 className="truncate text-sm font-semibold">{table.displayName}</h3>
{table.description && <p className="mt-1 truncate text-xs opacity-75">{table.description}</p>}
</div>
@ -56,7 +56,7 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
<div
key={columnKey}
className={`relative cursor-pointer rounded px-2 py-1 text-xs transition-colors ${
isSelected ? "bg-primary/20 text-blue-800 ring-2 ring-blue-500" : "text-gray-700 hover:bg-gray-100"
isSelected ? "bg-primary/20 text-primary ring-2 ring-ring" : "text-foreground hover:bg-muted"
}`}
onClick={() => onColumnClick(table.tableName, columnKey)}
>
@ -64,9 +64,9 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
<div className="flex items-center justify-between">
<span className="font-mono font-medium">{columnDisplayName}</span>
<span className="text-gray-500">{columnType}</span>
<span className="text-muted-foreground">{columnType}</span>
</div>
{column.description && <div className="mt-0.5 text-gray-500">{column.description}</div>}
{column.description && <div className="mt-0.5 text-muted-foreground">{column.description}</div>}
</div>
);
})}

View File

@ -74,7 +74,7 @@ export const TableSelector: React.FC<TableSelectorProps> = ({ companyCode, onTab
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-800"> </h3>
<h3 className="text-lg font-semibold text-foreground"> </h3>
<Button onClick={loadTables} variant="outline" size="sm" disabled={loading}>
{loading ? "로딩중..." : "새로고침"}
</Button>
@ -82,7 +82,7 @@ export const TableSelector: React.FC<TableSelectorProps> = ({ companyCode, onTab
{/* 검색 입력 */}
<div className="relative">
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground/70" />
<Input
placeholder="테이블명으로 검색..."
value={searchTerm}
@ -99,11 +99,11 @@ export const TableSelector: React.FC<TableSelectorProps> = ({ companyCode, onTab
<div className="max-h-96 space-y-2 overflow-y-auto">
{loading ? (
<div className="flex items-center justify-center py-8">
<div className="text-sm text-gray-500"> ...</div>
<div className="text-sm text-muted-foreground"> ...</div>
</div>
) : filteredTables.length === 0 ? (
<div className="flex items-center justify-center py-8">
<div className="text-center text-sm text-gray-500">
<div className="text-center text-sm text-muted-foreground">
{searchTerm ? "검색 결과가 없습니다." : "등록된 테이블이 없습니다."}
</div>
</div>
@ -114,14 +114,14 @@ export const TableSelector: React.FC<TableSelectorProps> = ({ companyCode, onTab
<Card
key={table.tableName}
className={`cursor-pointer transition-all hover:shadow-md ${
isSelected ? "cursor-not-allowed border-primary bg-accent opacity-60" : "hover:border-gray-300"
isSelected ? "cursor-not-allowed border-primary bg-accent opacity-60" : "hover:border-input"
}`}
onDoubleClick={() => !isSelected && handleAddTable(table)}
>
<CardHeader className="pb-2">
<div>
<CardTitle className="text-sm font-medium">{table.displayName}</CardTitle>
<div className="mt-1 text-xs text-gray-500">{table.columnCount} </div>
<div className="mt-1 text-xs text-muted-foreground">{table.columnCount} </div>
</div>
</CardHeader>
<CardContent className="pt-0">
@ -132,7 +132,7 @@ export const TableSelector: React.FC<TableSelectorProps> = ({ companyCode, onTab
{isSelected && <span className="font-medium text-primary">()</span>}
</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-muted-foreground">{table.description}</p>}
</div>
</CardContent>
</Card>
@ -142,7 +142,7 @@ export const TableSelector: React.FC<TableSelectorProps> = ({ companyCode, onTab
</div>
{/* 통계 정보 */}
<div className="rounded-lg bg-gray-50 p-3 text-xs text-muted-foreground">
<div className="rounded-lg bg-muted p-3 text-xs text-muted-foreground">
<div className="flex items-center justify-between">
<span> : {tables.length}</span>
{searchTerm && <span> : {filteredTables.length}</span>}

View File

@ -63,7 +63,7 @@ export const ConditionRenderer: React.FC<ConditionRendererProps> = ({
return (
<div className="space-y-2">
{conditions.length === 0 ? (
<div className="rounded-lg border border-dashed p-3 text-center text-xs text-gray-500">
<div className="rounded-lg border border-dashed p-3 text-center text-xs text-muted-foreground">
.
<br />
.
@ -92,7 +92,7 @@ export const ConditionRenderer: React.FC<ConditionRendererProps> = ({
)}
{/* 그룹 레벨에 따른 들여쓰기 */}
<div
className="flex items-center gap-2 rounded border-2 border-dashed border-blue-300 bg-accent/50 p-2"
className="flex items-center gap-2 rounded border-2 border-dashed border-primary/40 bg-accent/50 p-2"
style={{ marginLeft: `${(condition.groupLevel || 0) * 20}px` }}
>
<span className="font-mono text-sm text-primary">(</span>
@ -110,7 +110,7 @@ export const ConditionRenderer: React.FC<ConditionRendererProps> = ({
return (
<div key={condition.id} className="flex items-center gap-2">
<div
className="flex items-center gap-2 rounded border-2 border-dashed border-blue-300 bg-accent/50 p-2"
className="flex items-center gap-2 rounded border-2 border-dashed border-primary/40 bg-accent/50 p-2"
style={{ marginLeft: `${(condition.groupLevel || 0) * 20}px` }}
>
<span className="font-mono text-sm text-primary">)</span>

View File

@ -114,7 +114,7 @@ export const ActionConditionRenderer: React.FC<ActionConditionRendererProps> = (
value={settings.actions[actionIndex].conditions![condIndex - 1]?.logicalOperator || "AND"}
onValueChange={(value: "AND" | "OR") => updateLogicalOperator(condIndex - 1, value)}
>
<SelectTrigger className="h-6 w-24 border-green-200 bg-green-50 text-xs">
<SelectTrigger className="h-6 w-24 border-emerald-200 bg-emerald-50 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
@ -124,11 +124,11 @@ export const ActionConditionRenderer: React.FC<ActionConditionRendererProps> = (
</Select>
)}
<div
className="flex items-center gap-2 rounded border-2 border-dashed border-green-300 bg-green-50/50 p-1"
className="flex items-center gap-2 rounded border-2 border-dashed border-green-300 bg-emerald-50/50 p-1"
style={{ marginLeft: `${(condition.groupLevel || 0) * 15}px` }}
>
<span className="font-mono text-xs text-green-600">(</span>
<span className="text-xs text-green-600"> </span>
<span className="font-mono text-xs text-emerald-600">(</span>
<span className="text-xs text-emerald-600"> </span>
<Button
size="sm"
variant="ghost"
@ -147,11 +147,11 @@ export const ActionConditionRenderer: React.FC<ActionConditionRendererProps> = (
return (
<div className="flex items-center gap-2">
<div
className="flex items-center gap-2 rounded border-2 border-dashed border-green-300 bg-green-50/50 p-1"
className="flex items-center gap-2 rounded border-2 border-dashed border-green-300 bg-emerald-50/50 p-1"
style={{ marginLeft: `${(condition.groupLevel || 0) * 15}px` }}
>
<span className="font-mono text-xs text-green-600">)</span>
<span className="text-xs text-green-600"> </span>
<span className="font-mono text-xs text-emerald-600">)</span>
<span className="text-xs text-emerald-600"> </span>
<Button
size="sm"
variant="ghost"
@ -174,7 +174,7 @@ export const ActionConditionRenderer: React.FC<ActionConditionRendererProps> = (
value={settings.actions[actionIndex].conditions![condIndex - 1]?.logicalOperator || "AND"}
onValueChange={(value: "AND" | "OR") => updateLogicalOperator(condIndex - 1, value)}
>
<SelectTrigger className="h-6 w-24 border-green-200 bg-green-50 text-xs">
<SelectTrigger className="h-6 w-24 border-emerald-200 bg-emerald-50 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>

View File

@ -82,21 +82,21 @@ export const ActionConditionsSection: React.FC<ActionConditionsSectionProps> = (
<div className="mt-3">
<details className="group">
<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-muted hover:text-foreground ${
isConditionRequired && !hasValidConditions
? "border-red-300 bg-destructive/10 text-red-700"
: "border-gray-200 text-gray-700"
? "border-destructive/30 bg-destructive/10 text-destructive"
: "border-border text-foreground"
}`}
>
<div className="flex items-center gap-2">
🔍
{isConditionRequired ? (
<span className="rounded bg-destructive/20 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-destructive"></span>
) : (
<span className="text-gray-500">()</span>
<span className="text-muted-foreground">()</span>
)}
{action.conditions && action.conditions.length > 0 && (
<span className="rounded-full bg-primary/20 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-primary">
{action.conditions.length}
</span>
)}
@ -113,14 +113,14 @@ export const ActionConditionsSection: React.FC<ActionConditionsSectionProps> = (
e.stopPropagation();
clearAllConditions();
}}
className="h-5 w-5 p-0 text-red-500 hover:text-red-700"
className="h-5 w-5 p-0 text-destructive hover:text-destructive"
title="조건 모두 삭제"
>
<Trash2 className="h-3 w-3" />
</Button>
)}
</summary>
<div className="mt-2 space-y-2 border-l-2 border-gray-100 pl-4">
<div className="mt-2 space-y-2 border-l-2 border-border pl-4">
<div className="mb-2 flex items-center justify-between">
<div className="flex gap-1">
<Button size="sm" variant="outline" onClick={addActionCondition} className="h-6 text-xs">
@ -151,13 +151,13 @@ export const ActionConditionsSection: React.FC<ActionConditionsSectionProps> = (
<div
className={`rounded border p-3 text-xs ${
isConditionRequired
? "border-destructive/20 bg-destructive/10 text-red-700"
: "border-gray-200 bg-gray-50 text-muted-foreground"
? "border-destructive/20 bg-destructive/10 text-destructive"
: "border-border bg-muted text-muted-foreground"
}`}
>
{isConditionRequired ? (
<div className="flex items-start gap-2">
<span className="text-red-500"></span>
<span className="text-destructive"></span>
<div>
<div className="font-medium"> </div>
<div className="mt-1">

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