[agent-pipeline] pipe-20260309055714-23ry round-1

This commit is contained in:
DDD1542 2026-03-09 15:51:42 +09:00
parent 4f10b5e42d
commit 197ddf47cf
31 changed files with 228 additions and 260 deletions

View File

@ -3,7 +3,7 @@
import React, { useEffect, useState, useMemo } from "react";
import { useParams, useSearchParams } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react";
import { Loader2, FileQuestion, AlertTriangle } from "lucide-react";
import { screenApi } from "@/lib/api/screen";
import { ScreenDefinition, LayoutData, ComponentData } from "@/types/screen";
import { LayerDefinition } from "@/types/screen-management";
@ -597,8 +597,8 @@ function ScreenViewPage() {
if (loading) {
return (
<div className="from-muted to-muted/50 flex h-full min-h-[400px] w-full items-center justify-center bg-gradient-to-br">
<div className="border-border bg-background rounded-xl border p-8 text-center shadow-lg">
<div className="bg-muted/30 flex h-full min-h-[400px] w-full items-center justify-center">
<div className="border-border bg-background rounded-lg border p-8 text-center">
<Loader2 className="text-primary mx-auto h-10 w-10 animate-spin" />
<p className="text-foreground mt-4 font-medium"> ...</p>
</div>
@ -608,10 +608,10 @@ function ScreenViewPage() {
if (error || !screen) {
return (
<div className="from-muted to-muted/50 flex h-full min-h-[400px] w-full items-center justify-center bg-gradient-to-br">
<div className="border-border bg-background max-w-md rounded-xl border p-8 text-center shadow-lg">
<div className="from-destructive/20 to-warning/20 mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br shadow-sm">
<span className="text-3xl"></span>
<div className="bg-muted/30 flex h-full min-h-[400px] w-full items-center justify-center">
<div className="border-border bg-background max-w-md rounded-lg border p-8 text-center">
<div className="bg-destructive/10 mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full">
<AlertTriangle className="text-destructive h-10 w-10" />
</div>
<h2 className="text-foreground mb-3 text-xl font-bold"> </h2>
<p className="text-muted-foreground mb-6 leading-relaxed">{error || "요청하신 화면이 존재하지 않습니다."}</p>
@ -633,8 +633,8 @@ function ScreenViewPage() {
>
{/* 레이아웃 준비 중 로딩 표시 */}
{!layoutReady && (
<div className="from-muted to-muted/50 flex h-full w-full items-center justify-center bg-gradient-to-br">
<div className="border-border bg-background rounded-xl border p-8 text-center shadow-lg">
<div className="bg-muted/30 flex h-full w-full items-center justify-center">
<div className="border-border bg-background rounded-lg border p-8 text-center">
<Loader2 className="text-primary mx-auto h-8 w-8 animate-spin" />
<p className="text-foreground mt-4 text-sm font-medium"> ...</p>
</div>
@ -754,8 +754,8 @@ function ScreenViewPage() {
layoutReady && (
<div className="bg-background flex items-center justify-center" style={{ minHeight: screenHeight }}>
<div className="text-center">
<div className="bg-muted mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full shadow-sm">
<span className="text-2xl">📄</span>
<div className="bg-muted mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full">
<FileQuestion className="text-muted-foreground h-8 w-8" />
</div>
<h2 className="text-foreground mb-2 text-xl font-semibold"> </h2>
<p className="text-muted-foreground"> .</p>

View File

@ -132,7 +132,7 @@ export function CompanySwitcher({ onClose, isOpen = false }: CompanySwitcherProp
return (
<div className="space-y-4">
{/* 현재 회사 정보 */}
<div className="rounded-lg border bg-gradient-to-r from-primary/10 to-primary/5 p-4">
<div className="rounded-lg border bg-muted/40 p-4">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/20">
<Building2 className="h-5 w-5 text-primary" />

View File

@ -171,10 +171,10 @@ function renderWidget(element: DashboardElement) {
// === 기본 fallback ===
default:
return (
<div className="from-muted to-muted-foreground flex h-full w-full items-center justify-center bg-gradient-to-br p-4 text-white">
<div className="flex h-full w-full items-center justify-center bg-muted/30 p-4">
<div className="text-center">
<div className="mb-2 text-3xl"></div>
<div className="text-sm"> : {element.subtype}</div>
<div className="text-sm text-muted-foreground"> : {element.subtype}</div>
</div>
</div>
);

View File

@ -293,7 +293,7 @@ export default function DeliveryStatusWidget({ element, refreshInterval = 60000
};
return (
<div className="h-full w-full overflow-auto bg-gradient-to-br from-background to-primary/10 p-4">
<div className="h-full w-full overflow-auto bg-card p-4">
{/* 헤더 */}
<div className="mb-3 flex items-center justify-between">
<div>

View File

@ -140,7 +140,7 @@ export default function DeliveryTodayStatsWidget({ element }: DeliveryTodayStats
{/* 통계 카드 */}
<div className="flex flex-1 flex-col gap-4">
{/* 오늘 발송 */}
<div className="flex flex-1 flex-col items-center justify-center rounded-lg bg-gradient-to-br from-primary/10 to-primary/20 p-6">
<div className="flex flex-1 flex-col items-center justify-center rounded-lg border bg-primary/5 p-6">
<div className="mb-2 text-4xl">📤</div>
<p className="text-sm font-medium text-primary"> </p>
<p className="mt-2 text-4xl font-bold text-primary">{todayStats.shipped.toLocaleString()}</p>
@ -148,7 +148,7 @@ export default function DeliveryTodayStatsWidget({ element }: DeliveryTodayStats
</div>
{/* 오늘 도착 */}
<div className="flex flex-1 flex-col items-center justify-center rounded-lg bg-gradient-to-br from-success/10 to-success/20 p-6">
<div className="flex flex-1 flex-col items-center justify-center rounded-lg border bg-success/5 p-6">
<div className="mb-2 text-4xl">📥</div>
<p className="text-sm font-medium text-success"> </p>
<p className="mt-2 text-4xl font-bold text-success">{todayStats.delivered.toLocaleString()}</p>

View File

@ -251,9 +251,9 @@ export default function ExchangeWidget({
</div>
<div className="flex items-center justify-center gap-2">
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-border to-transparent" />
<div className="h-px flex-1 bg-border" />
<span className="text-xs text-muted-foreground"></span>
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-border to-transparent" />
<div className="h-px flex-1 bg-border" />
</div>
<div className="flex items-center gap-2">

View File

@ -249,7 +249,7 @@ export default function ListSummaryWidget({ element }: ListSummaryWidgetProps) {
}
return (
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-background to-primary/10 p-2">
<div className="flex h-full w-full flex-col overflow-hidden bg-card p-2">
{/* 헤더 */}
<div className="mb-2 flex flex-shrink-0 items-center justify-between">
<div className="flex-1">

View File

@ -115,7 +115,7 @@ export default function MaintenanceWidget() {
};
return (
<div className="flex h-full flex-col bg-gradient-to-br from-background to-primary/10">
<div className="flex h-full flex-col bg-card">
{/* 헤더 */}
<div className="border-b border-border bg-background px-4 py-3">
<div className="mb-3 flex items-center justify-between">

View File

@ -141,7 +141,7 @@ export default function StatusSummaryWidget({
element,
title = "상태 요약",
icon = "📊",
bgGradient = "from-background to-primary/10",
bgGradient = "",
statusConfig,
}: StatusSummaryWidgetProps) {
const [statusData, setStatusData] = useState<StatusData[]>([]);
@ -353,7 +353,7 @@ export default function StatusSummaryWidget({
const displayTitle = element.customTitle || (tableName ? `${translateTableName(tableName)} 현황` : title);
return (
<div className={`flex h-full w-full flex-col overflow-hidden bg-gradient-to-br ${bgGradient} p-2`}>
<div className="flex h-full w-full flex-col overflow-hidden bg-card p-2">
{/* 헤더 */}
<div className="mb-2 flex flex-shrink-0 items-center justify-between">
<div className="flex-1">

View File

@ -134,9 +134,9 @@ 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="pointer-events-auto absolute top-4 right-4 z-40 w-80 rounded-lg border border-border bg-background shadow-md">
{/* 헤더 */}
<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 justify-between rounded-t-lg border-b border-border bg-muted 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>

View File

@ -24,9 +24,9 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
canCreateConnection,
}) => {
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="pointer-events-auto absolute top-4 left-4 z-40 w-80 rounded-lg border border-border bg-background shadow-md">
{/* 헤더 */}
<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 justify-between rounded-t-lg border-b border-border bg-muted 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>
@ -104,24 +104,22 @@ export const SelectedTablesPanel: React.FC<SelectedTablesPanelProps> = ({
</div>
{/* 액션 버튼 */}
<div className="flex gap-2 border-t border-primary/10 p-3">
<div className="flex gap-2 border-t border-border 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 ${
className={`flex flex-1 items-center justify-center gap-1 rounded-md px-3 py-2 text-xs font-medium transition-colors ${
canCreateConnection
? "bg-accent0 text-white hover:bg-primary"
: "cursor-not-allowed bg-muted/60 text-muted-foreground"
? "bg-primary text-primary-foreground hover:bg-primary/90"
: "cursor-not-allowed bg-muted text-muted-foreground"
}`}
>
<span>🔗</span>
<span> </span>
</button>
<button
onClick={onClear}
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"
className="flex flex-1 items-center justify-center gap-1 rounded-md border border-border bg-background px-3 py-2 text-xs font-medium text-muted-foreground hover:bg-muted"
>
<span>🗑</span>
<span></span>
</button>
</div>

View File

@ -410,7 +410,7 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
setToShowMappedOnly(false);
setToShowUnmappedOnly(true);
}}
className="h-7 bg-amber-100 text-xs text-orange-700 hover:bg-orange-200"
className="h-7 text-xs"
>
</Button>
@ -424,7 +424,7 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
setToShowMappedOnly(true);
setToShowUnmappedOnly(false);
}}
className="h-7 bg-emerald-100 text-xs text-emerald-700 hover:bg-emerald-200"
className="h-7 text-xs"
>
</Button>
@ -440,7 +440,7 @@ export const InsertFieldMappingPanel: React.FC<InsertFieldMappingPanelProps> = (
</Card>
{/* 매핑 통계 */}
<Card className="mt-4 bg-gradient-to-r from-muted to-muted py-2">
<Card className="mt-4 bg-muted/50 py-2">
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">

View File

@ -32,10 +32,10 @@ export const DataConnectionDesigner: React.FC = () => {
const { isMobile, isTablet } = useResponsive();
return (
<div className="h-screen bg-gradient-to-br from-slate-50 to-muted">
<div className="bg-white border-b border-border px-6 py-4">
<div className="h-screen bg-background">
<div className="bg-background border-b border-border px-6 py-4">
<h1 className="text-2xl font-bold text-foreground">
🎨 -
-
</h1>
<p className="text-muted-foreground mt-1">

View File

@ -256,7 +256,7 @@ export const DataflowVisualization: React.FC<DataflowVisualizationProps> = ({ st
</div>
{/* 통계 요약 */}
<Card className="border-border bg-gradient-to-r from-muted to-muted/50">
<Card className="border-border bg-muted/50">
<CardContent>
<div className="p-4">
<div className="flex flex-col items-center justify-around gap-4 text-center sm:flex-row sm:gap-0">

View File

@ -30,49 +30,49 @@ export default function ConfirmDeleteModal({
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="bg-white rounded-xl shadow-2xl max-w-md w-full">
<div className="bg-background rounded-lg shadow-lg border max-w-[95vw] sm:max-w-[500px] w-full">
{/* 헤더 */}
<div className="bg-gradient-to-r from-red-500 to-red-600 px-6 py-4 flex items-center justify-between rounded-t-xl">
<div className="flex items-center gap-3">
<AlertTriangle className="w-6 h-6 text-white" />
<h2 className="text-xl font-bold text-white">{title}</h2>
<div className="flex items-center justify-between px-6 py-4 border-b">
<div className="flex items-center gap-2">
<AlertTriangle className="w-5 h-5 text-destructive" />
<h2 className="text-base sm:text-lg font-semibold">{title}</h2>
</div>
<button
onClick={onClose}
className="text-white hover:bg-white/20 rounded-lg p-2 transition"
className="text-muted-foreground hover:text-foreground rounded-md p-1 transition-colors"
>
<X className="w-5 h-5" />
<X className="w-4 h-4" />
</button>
</div>
{/* 내용 */}
<div className="p-6 space-y-4">
<p className="text-foreground">{message}</p>
<div className="px-6 py-4 space-y-3">
<p className="text-sm sm:text-base text-foreground">{message}</p>
{itemName && (
<div className="bg-destructive/10 border border-destructive/20 rounded-lg p-3">
<p className="text-sm font-medium text-red-800">
<div className="bg-destructive/10 border border-destructive/20 rounded-md p-3">
<p className="text-xs sm:text-sm font-medium text-destructive">
: <span className="font-bold">{itemName}</span>
</p>
</div>
)}
<p className="text-sm text-muted-foreground">
<p className="text-xs sm:text-sm text-muted-foreground">
. ?
</p>
</div>
{/* 버튼 */}
<div className="flex gap-3 px-6 pb-6">
<div className="flex gap-2 px-6 pb-6">
<Button
onClick={onClose}
variant="outline"
className="flex-1"
className="h-8 flex-1 text-xs sm:h-10 sm:text-sm"
>
</Button>
<Button
onClick={handleConfirm}
variant="destructive"
className="flex-1"
className="h-8 flex-1 text-xs sm:h-10 sm:text-sm"
>
</Button>

View File

@ -133,20 +133,20 @@ export default function MailAccountModal({
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="bg-white rounded-xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
<div className="bg-background rounded-lg shadow-lg border max-w-[95vw] sm:max-w-2xl w-full max-h-[90vh] overflow-y-auto">
{/* 헤더 */}
<div className="sticky top-0 bg-gradient-to-r from-primary to-primary px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<Mail className="w-6 h-6 text-white" />
<h2 className="text-xl font-bold text-white">
<div className="sticky top-0 bg-background border-b px-6 py-4 flex items-center justify-between rounded-t-lg">
<div className="flex items-center gap-2">
<Mail className="w-5 h-5 text-primary" />
<h2 className="text-base sm:text-lg font-semibold">
{mode === 'create' ? '새 메일 계정 추가' : '메일 계정 수정'}
</h2>
</div>
<button
onClick={onClose}
className="text-white hover:bg-white/20 rounded-lg p-2 transition"
className="text-muted-foreground hover:text-foreground rounded-md p-1 transition-colors"
>
<X className="w-5 h-5" />
<X className="w-4 h-4" />
</button>
</div>
@ -374,12 +374,12 @@ export default function MailAccountModal({
)}
{/* 버튼 */}
<div className="flex gap-3 pt-4">
<div className="flex gap-2 pt-4">
<Button
type="button"
onClick={onClose}
variant="outline"
className="flex-1"
className="h-8 flex-1 text-xs sm:h-10 sm:text-sm"
disabled={isSubmitting}
>
@ -387,7 +387,7 @@ export default function MailAccountModal({
<Button
type="submit"
variant="default"
className="flex-1"
className="h-8 flex-1 text-xs sm:h-10 sm:text-sm"
disabled={isSubmitting}
>
{isSubmitting ? (

View File

@ -82,7 +82,7 @@ export default function MailAccountTable({
if (accounts.length === 0) {
return (
<div className="bg-gradient-to-br from-muted to-muted rounded-xl p-12 text-center border-2 border-dashed border">
<div className="bg-muted/30 rounded-lg p-12 text-center border-2 border-dashed border">
<Mail className="w-16 h-16 text-muted-foreground/70 mx-auto mb-4" />
<p className="text-lg font-medium text-muted-foreground mb-2">
@ -109,10 +109,10 @@ export default function MailAccountTable({
</div>
{/* 테이블 */}
<div className="bg-white rounded-xl shadow-sm border border overflow-hidden">
<div className="bg-card rounded-lg shadow-sm border overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gradient-to-r from-slate-50 to-muted border-b border">
<thead className="bg-muted/50 border-b">
<tr>
<th
className="px-6 py-4 text-left text-sm font-semibold text-foreground cursor-pointer hover:bg-muted transition"
@ -170,7 +170,7 @@ export default function MailAccountTable({
{sortedAccounts.map((account) => (
<tr
key={account.id}
className="hover:bg-amber-50/50 transition-colors"
className="hover:bg-muted/40 transition-colors"
>
<td className="px-6 py-4">
<div className="font-medium text-foreground">{account.name}</div>

View File

@ -275,7 +275,7 @@ export default function MailDesigner({
return (
<div className="flex items-center justify-center h-screen bg-muted/30">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-orange-500 mx-auto mb-4"></div>
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
<p className="text-muted-foreground">릿 ...</p>
</div>
</div>
@ -374,8 +374,8 @@ export default function MailDesigner({
{/* 중앙: 캔버스 */}
<div className="flex-1 p-8 overflow-y-auto">
<Card className="max-w-3xl mx-auto">
<CardHeader className="bg-gradient-to-r from-muted to-muted border-b">
<Card className="max-w-3xl mx-auto">
<CardHeader className="bg-muted/40 border-b">
<CardTitle className="flex items-center justify-between">
<span> </span>
<span className="text-sm text-muted-foreground font-normal">

View File

@ -40,12 +40,12 @@ export default function MailTemplateCard({
};
return (
<div className="group bg-white rounded-xl border border shadow-sm hover:shadow-lg transition-all duration-300 overflow-hidden">
<div className="group bg-card rounded-lg border shadow-sm hover:shadow-md transition-all duration-300 overflow-hidden">
{/* 헤더 */}
<div className="bg-gradient-to-r from-muted to-muted p-4 border-b border">
<div className="bg-muted/40 p-4 border-b">
<div className="flex items-start justify-between">
<div className="flex items-start gap-3 flex-1">
<div className="p-2 bg-white rounded-lg shadow-sm">
<div className="p-2 bg-background rounded-md border">
<Mail className="w-5 h-5 text-primary" />
</div>
<div className="flex-1 min-w-0">
@ -71,7 +71,7 @@ export default function MailTemplateCard({
{/* 본문 미리보기 */}
<div className="p-4 space-y-3">
<div className="bg-muted/30 rounded-lg p-3 border border min-h-[100px]">
<div className="bg-muted/30 rounded-md p-3 border min-h-[100px]">
<p className="text-xs text-muted-foreground mb-2"> {template.components.length}</p>
<div className="space-y-1">
{template.components.slice(0, 3).map((component, idx) => (

View File

@ -47,18 +47,18 @@ export default function MailTemplateEditorModal({
return (
<div className="fixed inset-0 z-50 bg-white">
{/* 헤더 */}
<div className="sticky top-0 bg-gradient-to-r from-primary to-primary px-6 py-4 flex items-center justify-between shadow-lg z-10">
<div className="flex items-center gap-3">
<h2 className="text-xl font-bold text-white">
<div className="sticky top-0 bg-background border-b px-6 py-4 flex items-center justify-between shadow-sm z-10">
<div className="flex items-center gap-2">
<h2 className="text-base sm:text-lg font-semibold">
{mode === 'create' ? '새 메일 템플릿 만들기' : '메일 템플릿 수정'}
</h2>
</div>
<button
onClick={onClose}
disabled={isSaving}
className="text-white hover:bg-white/20 rounded-lg p-2 transition disabled:opacity-50"
className="text-muted-foreground hover:text-foreground rounded-md p-1 transition-colors disabled:opacity-50"
>
<X className="w-5 h-5" />
<X className="w-4 h-4" />
</button>
</div>

View File

@ -490,7 +490,7 @@ export default function PopCanvas({
return (
<div className="flex h-full flex-col bg-muted">
{/* 상단 컨트롤 */}
<div className="flex items-center gap-2 border-b bg-white px-4 py-2">
<div className="flex items-center gap-2 border-b bg-background px-4 py-2">
{/* 모드 프리셋 버튼 */}
<div className="flex gap-1">
{VIEWPORT_PRESETS.map((preset) => {
@ -517,7 +517,7 @@ export default function PopCanvas({
})}
</div>
<div className="h-4 w-px bg-muted/60" />
<div className="h-4 w-px bg-border" />
{/* 고정/되돌리기 버튼 (기본 모드 아닐 때만 표시) */}
{currentMode !== DEFAULT_PRESET && (
@ -546,14 +546,14 @@ export default function PopCanvas({
</>
)}
<div className="h-4 w-px bg-muted/60" />
<div className="h-4 w-px bg-border" />
{/* 해상도 표시 */}
<div className="text-xs text-muted-foreground">
{customWidth} × {Math.round(dynamicCanvasHeight)}
</div>
<div className="h-4 w-px bg-muted/60" />
<div className="h-4 w-px bg-border" />
{/* Gap 프리셋 선택 */}
<div className="flex items-center gap-2">
@ -610,7 +610,7 @@ export default function PopCanvas({
</Button>
</div>
<div className="h-4 w-px bg-muted/60" />
<div className="h-4 w-px bg-border" />
{/* 그리드 가이드 토글 */}
<Button
@ -728,7 +728,7 @@ export default function PopCanvas({
<div
ref={canvasRef}
className={cn(
"relative rounded-lg border-2 bg-white shadow-xl overflow-visible",
"relative rounded-lg border-2 bg-background shadow-xl overflow-visible",
canDrop && isOver && "ring-4 ring-primary/20"
)}
style={{
@ -803,7 +803,7 @@ export default function PopCanvas({
</div>
{/* 하단 정보 */}
<div className="flex items-center justify-between border-t bg-white px-4 py-2">
<div className="flex items-center justify-between border-t bg-background px-4 py-2">
<div className="text-xs text-muted-foreground">
{breakpoint.label} - {breakpoint.columns} ( : {breakpoint.rowHeight}px)
</div>
@ -889,7 +889,7 @@ function ReviewItem({
"flex flex-col gap-1 rounded-md border-2 p-2 cursor-pointer transition-all",
isSelected
? "border-primary bg-primary/10 shadow-sm"
: "border-primary/20 bg-white hover:border-primary/60 hover:bg-primary/10"
: "border-primary/20 bg-background hover:border-primary/60 hover:bg-primary/10"
)}
onClick={(e) => {
e.stopPropagation();
@ -1026,7 +1026,7 @@ function HiddenItem({
<div
ref={drag}
className={cn(
"rounded-md border-2 bg-white p-2 cursor-move transition-all opacity-60",
"rounded-md border-2 bg-background p-2 cursor-move transition-all opacity-60",
isSelected
? "border-primary ring-2 ring-primary/30"
: "border-input hover:border-input",

View File

@ -57,7 +57,7 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
isGeneratingMultilang = false,
}) => {
return (
<div className="flex items-center justify-between border-b border-border bg-gradient-to-r from-muted to-background px-4 py-3 shadow-sm">
<div className="flex items-center justify-between border-b border-border bg-background px-4 py-3 shadow-sm">
{/* 좌측: 네비게이션 및 화면 정보 */}
<div className="flex items-center space-x-4">
<Button variant="ghost" size="sm" onClick={onBack} className="flex items-center space-x-2">
@ -65,7 +65,7 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
<span></span>
</Button>
<div className="h-6 w-px bg-muted/60" />
<div className="h-6 w-px bg-border" />
<div className="flex items-center space-x-3">
<Menu className="h-5 w-5 text-muted-foreground" />
@ -229,7 +229,7 @@ export const DesignerToolbar: React.FC<DesignerToolbarProps> = ({
<span className="hidden sm:inline"></span>
</Button>
<div className="h-6 w-px bg-muted/60" />
<div className="h-6 w-px bg-border" />
{onGenerateMultilang && (
<Button

View File

@ -2287,7 +2287,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
)}
</div>
) : (
<Folder className="h-4 w-4 text-muted-foreground/70" />
<Folder className="h-4 w-4 text-muted-foreground" />
)}
</Button>
</div>
@ -2400,7 +2400,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
return (
<div
className={cn(
"flex h-full flex-col rounded-xl border border-border/60 bg-gradient-to-br from-white to-muted/30 shadow-sm",
"flex h-full flex-col rounded-xl border border-border/60 bg-background shadow-sm",
className,
)}
style={{ ...style, minHeight: "680px" }}
@ -2528,7 +2528,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
<>
<div className="overflow-hidden rounded-lg border border-border/60 bg-white shadow-sm">
<Table style={{ tableLayout: "fixed" }}>
<TableHeader className="from-muted/50 to-muted border-primary/20 border-b-2 bg-gradient-to-b">
<TableHeader className="bg-muted/50 border-primary/20 border-b-2">
<TableRow>
{/* 체크박스 컬럼 (삭제 기능이 활성화된 경우) */}
{component.enableDelete && (
@ -2633,7 +2633,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
</TableRow>
) : data.length > 0 ? (
data.map((row, rowIndex) => (
<TableRow key={rowIndex} className="transition-all duration-200 hover:bg-amber-100">
<TableRow key={rowIndex} className="transition-colors duration-150 hover:bg-muted/50">
{/* 체크박스 셀 (삭제 기능이 활성화된 경우) */}
{component.enableDelete && (
<TableCell className="px-4" style={{ width: "48px", minWidth: "48px", maxWidth: "48px" }}>
@ -2665,7 +2665,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
className="h-32 text-center"
>
<div className="text-muted-foreground flex flex-col items-center gap-2">
<Database className="h-8 w-8" />
<Database className="h-6 w-6" />
<p> </p>
<p className="text-xs"> </p>
</div>
@ -2678,7 +2678,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
{/* 페이지네이션 */}
{component.pagination?.enabled && totalPages > 1 && (
<div className="mt-auto border-t border-border/60 bg-gradient-to-r from-muted to-slate-50">
<div className="mt-auto border-t border-border/60 bg-muted/30">
<div className="flex items-center justify-between px-6 py-3">
{component.pagination.showPageInfo && (
<div className="text-muted-foreground text-sm">
@ -2743,7 +2743,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
) : (
<div className="flex flex-1 items-center justify-center">
<div className="text-muted-foreground flex flex-col items-center gap-2">
<Database className="h-8 w-8" />
<Database className="h-6 w-6" />
<p className="text-sm"> </p>
<p className="text-xs"> </p>
</div>

View File

@ -1767,7 +1767,7 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
</DialogHeader>
<ScreenPreviewProvider isPreviewMode={true}>
<TableOptionsProvider>
<div className="flex flex-1 items-center justify-center overflow-hidden bg-gradient-to-br from-muted to-slate-100 p-6">
<div className="flex flex-1 items-center justify-center overflow-hidden bg-muted/30 p-6">
{isLoadingPreview ? (
<div className="flex h-full items-center justify-center">
<div className="text-center">

View File

@ -319,7 +319,7 @@ const ScreenPreview: React.FC<{ layoutSummary: ScreenLayoutSummary; screenType:
// 그리드 화면 일러스트
if (screenType === "grid") {
return (
<div className="flex h-full flex-col gap-2 rounded-lg border border-slate-200 bg-gradient-to-b from-slate-50 to-background p-3">
<div className="flex h-full flex-col gap-2 rounded-lg border border-slate-200 bg-muted/30 p-3">
{/* 상단 툴바 */}
<div className="flex items-center gap-2">
<div className="h-4 w-16 rounded bg-pink-400/80 shadow-sm" />
@ -362,7 +362,7 @@ const ScreenPreview: React.FC<{ layoutSummary: ScreenLayoutSummary; screenType:
// 폼 화면 일러스트
if (screenType === "form") {
return (
<div className="flex h-full flex-col gap-3 rounded-lg border border-slate-200 bg-gradient-to-b from-slate-50 to-background p-3">
<div className="flex h-full flex-col gap-3 rounded-lg border border-slate-200 bg-muted/30 p-3">
{/* 폼 필드들 */}
{[...Array(6)].map((_, i) => (
<div key={i} className="flex items-center gap-3">
@ -386,7 +386,7 @@ const ScreenPreview: React.FC<{ layoutSummary: ScreenLayoutSummary; screenType:
// 대시보드 화면 일러스트
if (screenType === "dashboard") {
return (
<div className="grid h-full grid-cols-2 gap-2 rounded-lg border border-slate-200 bg-gradient-to-b from-slate-50 to-background p-3">
<div className="grid h-full grid-cols-2 gap-2 rounded-lg border border-slate-200 bg-muted/30 p-3">
{/* 카드/차트들 */}
<div className="rounded-lg bg-emerald-100 p-2 shadow-sm">
<div className="mb-2 h-2.5 w-10 rounded bg-emerald-400" />
@ -419,7 +419,7 @@ const ScreenPreview: React.FC<{ layoutSummary: ScreenLayoutSummary; screenType:
// 액션 화면 일러스트 (버튼 중심)
if (screenType === "action") {
return (
<div className="flex h-full flex-col items-center justify-center gap-4 rounded-lg border border-slate-200 bg-gradient-to-b from-slate-50 to-background p-3">
<div className="flex h-full flex-col items-center justify-center gap-4 rounded-lg border border-slate-200 bg-muted/30 p-3">
<div className="rounded-full bg-slate-100 p-4 text-slate-400">
<MousePointer2 className="h-10 w-10" />
</div>
@ -438,7 +438,7 @@ const ScreenPreview: React.FC<{ layoutSummary: ScreenLayoutSummary; screenType:
// 기본 (알 수 없는 타입)
return (
<div className="flex h-full flex-col items-center justify-center gap-3 rounded-lg border border-slate-200 bg-gradient-to-b from-slate-50 to-background text-slate-400">
<div className="flex h-full flex-col items-center justify-center gap-3 rounded-lg border border-slate-200 bg-muted/30 text-slate-400">
<div className="rounded-full bg-slate-100 p-4">
{getScreenTypeIcon(screenType)}
</div>

View File

@ -2,8 +2,7 @@
import React from "react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Database, Layout, Cog, Settings, Palette, Monitor } from "lucide-react";
import { Layout } from "lucide-react";
import { cn } from "@/lib/utils";
export interface ToolbarButton {
@ -39,8 +38,8 @@ export const LeftV2Toolbar: React.FC<LeftV2ToolbarProps> = ({ buttons, panelStat
className={cn(
"flex h-14 w-14 flex-col items-center justify-center gap-1 rounded-lg transition-all duration-200",
isActive
? "bg-gradient-to-br from-primary/50 to-primary text-white shadow-lg hover:from-primary hover:to-blue-700"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
? "bg-primary text-primary-foreground shadow-md hover:bg-primary/90"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground",
)}
>
<div className="relative">
@ -58,9 +57,9 @@ export const LeftV2Toolbar: React.FC<LeftV2ToolbarProps> = ({ buttons, panelStat
};
return (
<div className="flex h-full w-[60px] flex-col border-r border-slate-200 bg-white">
<div className="border-border bg-background flex h-full w-[60px] flex-col border-r">
{/* 입력/소스 그룹 */}
<div className="flex flex-col gap-1 border-b border-slate-200 p-1">{sourceButtons.map(renderButton)}</div>
<div className="border-border flex flex-col gap-1 border-b p-1">{sourceButtons.map(renderButton)}</div>
{/* 편집/설정 그룹 */}
<div className="flex flex-col gap-1 p-1">{editorButtons.map(renderButton)}</div>

View File

@ -158,7 +158,7 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
};
return (
<div className="flex h-14 items-center justify-between border-b border-border bg-gradient-to-r from-muted to-background px-4 shadow-sm">
<div className="flex h-14 items-center justify-between border-b border-border bg-background px-4 shadow-sm">
{/* 좌측: 네비게이션 + 패널 토글 + 화면 정보 */}
<div className="flex items-center space-x-4">
<Button variant="ghost" size="sm" onClick={onBack} className="flex items-center space-x-2">
@ -166,7 +166,7 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
<span></span>
</Button>
{onTogglePanel && <div className="h-6 w-px bg-muted/60" />}
{onTogglePanel && <div className="h-6 w-px bg-border" />}
{/* 패널 토글 버튼 */}
{onTogglePanel && (
@ -186,7 +186,7 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
</Button>
)}
<div className="h-6 w-px bg-muted/60" />
<div className="h-6 w-px bg-border" />
<div className="flex items-center space-x-3">
<div>
@ -203,10 +203,10 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
{/* 해상도 선택 드롭다운 */}
{screenResolution && (
<>
<div className="h-6 w-px bg-muted/60" />
<div className="h-6 w-px bg-border" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="flex items-center space-x-2 rounded-md bg-primary/10 px-3 py-1.5 transition-colors hover:bg-primary/10">
<button className="flex items-center space-x-2 rounded-md bg-primary/10 px-3 py-1.5 transition-colors hover:bg-primary/20">
{getCategoryIcon(screenResolution.category || "desktop")}
<span className="text-sm font-medium text-primary">{screenResolution.name}</span>
<span className="text-xs text-primary">
@ -322,7 +322,7 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
{/* 격자 설정 */}
{gridSettings && onGridSettingsChange && (
<>
<div className="h-6 w-px bg-muted/60" />
<div className="h-6 w-px bg-border" />
<div className="flex items-center space-x-2 rounded-md bg-muted px-3 py-1.5">
<Grid3X3 className="h-4 w-4 text-muted-foreground" />
<div className="flex items-center space-x-3">
@ -370,7 +370,7 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => onAlign("right")} title="우측 정렬 (Alt+R)">
<AlignEndVertical className="h-3.5 w-3.5" />
</Button>
<div className="mx-0.5 h-4 w-px bg-blue-200" />
<div className="mx-0.5 h-4 w-px bg-border" />
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => onAlign("top")} title="상단 정렬 (Alt+T)">
<AlignStartHorizontal className="h-3.5 w-3.5" />
</Button>
@ -386,7 +386,7 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
{/* 배분 (3개 이상 선택 시) */}
{onDistribute && selectedCount >= 3 && (
<>
<div className="mx-1 h-4 w-px bg-blue-200" />
<div className="mx-1 h-4 w-px bg-border" />
<span className="mr-1 text-xs font-medium text-primary"></span>
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => onDistribute("horizontal")} title="가로 균등 배분 (Alt+H)">
<AlignHorizontalSpaceAround className="h-3.5 w-3.5" />
@ -400,7 +400,7 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
{/* 크기 맞추기 */}
{onMatchSize && (
<>
<div className="mx-1 h-4 w-px bg-blue-200" />
<div className="mx-1 h-4 w-px bg-border" />
<span className="mr-1 text-xs font-medium text-primary"></span>
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => onMatchSize("width")} title="너비 맞추기 (Alt+W)">
<RulerIcon className="h-3.5 w-3.5" />
@ -414,7 +414,7 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
</>
)}
<div className="mx-1 h-4 w-px bg-blue-200" />
<div className="mx-1 h-4 w-px bg-border" />
<span className="text-xs text-primary">{selectedCount} </span>
</div>
)}

View File

@ -709,54 +709,33 @@ export function FileUpload({ component, onUpdateComponent, onFileUpload, userInf
<div className="w-full space-y-4">
{/* 드래그 앤 드롭 영역 */}
<div
className={`group relative rounded-2xl border-2 border-dashed p-10 text-center transition-all duration-300 ${
className={`group relative rounded-lg border-2 border-dashed p-8 text-center transition-colors duration-150 ${
isDragOver
? "border-primary scale-105 bg-gradient-to-br from-primary/10/90 to-primary/5/80 shadow-xl shadow-blue-500/20"
: "border-input/60 bg-gradient-to-br from-muted/80 to-primary/5/40 hover:border-primary/60/80 hover:bg-gradient-to-br hover:from-primary/5/90 hover:to-indigo-50/60 hover:shadow-lg hover:shadow-blue-500/10"
? "border-primary bg-muted/50"
: "border-border bg-muted/30 hover:border-primary/60 hover:bg-muted/40"
}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<div className="relative">
<Upload
className={`mx-auto mb-4 h-16 w-16 transition-all duration-300 ${
isDragOver ? "scale-110 text-primary" : "text-muted-foreground/70 group-hover:scale-105 group-hover:text-primary"
}`}
/>
<div className="absolute inset-0 flex items-center justify-center">
<div
className={`h-20 w-20 rounded-full transition-all duration-300 ${
isDragOver
? "scale-110 bg-blue-200/80"
: "bg-primary/20/50 opacity-0 group-hover:scale-110 group-hover:opacity-100"
}`}
></div>
</div>
</div>
<Upload
className={`mx-auto mb-3 h-10 w-10 transition-colors duration-150 ${
isDragOver ? "text-primary" : "text-muted-foreground group-hover:text-primary"
}`}
/>
<p
className={`mb-2 text-xl font-semibold transition-colors duration-300 ${
isDragOver ? "text-primary" : "group-hover:text-primary text-foreground"
className={`mb-1 text-sm font-medium transition-colors duration-150 ${
isDragOver ? "text-primary" : "text-foreground"
}`}
>
{fileConfig.dragDropText || "파일을 드래그하여 업로드하세요"}
</p>
<p
className={`mb-4 text-sm transition-colors duration-300 ${
isDragOver ? "text-primary" : "text-muted-foreground group-hover:text-primary"
}`}
>
</p>
<p className="mb-4 text-xs text-muted-foreground"> </p>
<Button
variant="outline"
onClick={handleFileInputClick}
className={`mb-4 rounded-xl border-2 transition-all duration-200 ${
isDragOver
? "bg-accent text-primary border-primary/60 shadow-md"
: "hover:bg-accent/50 border-input/60 hover:border-primary/60/60 hover:shadow-sm"
}`}
className="mb-4"
>
<Upload className="mr-2 h-4 w-4" />
{fileConfig?.uploadButtonText || "파일 선택"}
@ -781,66 +760,55 @@ export function FileUpload({ component, onUpdateComponent, onFileUpload, userInf
{/* 업로드된 파일 목록 */}
{uploadedFiles.length > 0 && (
<div className="space-y-4">
<div className="border-primary/20/40 flex items-center justify-between rounded-xl border bg-gradient-to-r from-primary/5/80 to-indigo-50/60 px-4 py-3">
<div className="flex items-center space-x-3">
<div className="bg-primary/20 flex h-8 w-8 items-center justify-center rounded-full">
<File className="text-primary h-4 w-4" />
</div>
<div className="space-y-3">
<div className="flex items-center justify-between rounded-md border border-border bg-muted/20 px-4 py-2.5">
<div className="flex items-center space-x-2">
<File className="h-4 w-4 text-muted-foreground" />
<div>
<h4 className="text-lg font-semibold text-foreground">
<span className="text-sm font-medium text-foreground">
({uploadedFiles.length}/{fileConfig.maxFiles})
</h4>
<p className="text-muted-foreground text-sm">
</span>
<span className="ml-2 text-xs text-muted-foreground">
{formatFileSize(uploadedFiles.reduce((sum, file) => sum + file.fileSize, 0))}
</p>
</span>
</div>
</div>
<Button
variant="outline"
size="sm"
className="border-primary/20/60 hover:bg-accent/80 rounded-lg bg-white/80"
>
<Button variant="outline" size="sm">
</Button>
</div>
<div className="space-y-3">
<div className="space-y-2">
{uploadedFiles.map((fileInfo) => (
<div
key={fileInfo.objid}
className="group hover:bg-accent/30 relative flex items-center justify-between rounded-xl border border-border/60 bg-white/90 p-4 shadow-sm transition-all duration-200 hover:border-primary/40/60 hover:shadow-md"
className="group flex items-center justify-between rounded-md border border-border bg-background p-3 transition-colors duration-150 hover:bg-accent/30"
>
<div className="flex flex-1 items-center space-x-4">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-muted to-muted/80 shadow-sm">
<div className="flex flex-1 items-center space-x-3">
<div className="flex h-9 w-9 items-center justify-center rounded-md bg-muted">
{getFileIcon(fileInfo.fileExt)}
</div>
<div className="min-w-0 flex-1">
<p className="group-hover:text-primary truncate text-base font-semibold text-foreground transition-colors duration-200">
<p className="truncate text-sm font-medium text-foreground">
{fileInfo.realFileName}
</p>
<div className="mt-1 flex items-center space-x-3 text-sm text-muted-foreground">
<span className="flex items-center space-x-1">
<div className="h-1.5 w-1.5 rounded-full bg-primary/70"></div>
<span className="font-medium">{formatFileSize(fileInfo.fileSize)}</span>
</span>
<span className="flex items-center space-x-1">
<div className="h-1.5 w-1.5 rounded-full bg-muted-foreground"></div>
<span className="rounded-md bg-muted px-2 py-1 text-xs font-medium">
{fileInfo.fileExt.toUpperCase()}
</span>
<div className="mt-0.5 flex items-center space-x-2 text-xs text-muted-foreground">
<span>{formatFileSize(fileInfo.fileSize)}</span>
<span>·</span>
<span className="rounded bg-muted px-1.5 py-0.5 font-medium">
{fileInfo.fileExt.toUpperCase()}
</span>
{fileInfo.writer && (
<span className="flex items-center space-x-1">
<div className="h-1.5 w-1.5 rounded-full bg-green-400"></div>
<span className="text-xs">{fileInfo.writer}</span>
</span>
<>
<span>·</span>
<span>{fileInfo.writer}</span>
</>
)}
</div>
{/* 업로드 진행률 */}
{fileInfo.isUploading && fileConfig.showProgress && (
<div className="mt-2 h-1 w-full rounded-full bg-muted/80">
<div className="mt-1.5 h-1 w-full rounded-full bg-muted">
<div
className="h-1 rounded-full bg-primary transition-all duration-300"
style={{ width: `${fileInfo.uploadProgress || 0}%` }}
@ -850,32 +818,32 @@ export function FileUpload({ component, onUpdateComponent, onFileUpload, userInf
{/* 에러 메시지 */}
{fileInfo.hasError && (
<div className="bg-destructive/10 mt-2 flex items-center space-x-2 rounded-md p-2 text-sm text-destructive">
<AlertCircle className="h-4 w-4 flex-shrink-0" />
<div className="mt-1.5 flex items-center space-x-1.5 rounded-md bg-destructive/10 p-1.5 text-xs text-destructive">
<AlertCircle className="h-3.5 w-3.5 flex-shrink-0" />
<span>{fileInfo.errorMessage}</span>
</div>
)}
</div>
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center space-x-1">
{/* 상태 표시 */}
{fileInfo.isUploading && (
<div className="bg-accent flex items-center space-x-2 rounded-lg px-3 py-2">
<Loader2 className="h-4 w-4 animate-spin text-primary" />
<span className="text-primary text-xs font-medium"> ...</span>
<div className="flex items-center space-x-1.5 rounded-md bg-muted px-2 py-1">
<Loader2 className="h-3.5 w-3.5 animate-spin text-primary" />
<span className="text-xs text-muted-foreground"> ...</span>
</div>
)}
{fileInfo.status === "ACTIVE" && (
<div className="flex items-center space-x-2 rounded-lg bg-emerald-50 px-3 py-2">
<CheckCircle className="h-4 w-4 text-emerald-500" />
<span className="text-xs font-medium text-emerald-600"></span>
<div className="flex items-center space-x-1.5 rounded-md bg-muted px-2 py-1">
<CheckCircle className="h-3.5 w-3.5 text-success" />
<span className="text-xs text-muted-foreground"></span>
</div>
)}
{fileInfo.hasError && (
<div className="bg-destructive/10 flex items-center space-x-2 rounded-lg px-3 py-2">
<AlertCircle className="h-4 w-4 text-destructive" />
<span className="text-destructive text-xs font-medium"></span>
<div className="flex items-center space-x-1.5 rounded-md bg-destructive/10 px-2 py-1">
<AlertCircle className="h-3.5 w-3.5 text-destructive" />
<span className="text-xs text-destructive"></span>
</div>
)}
@ -887,7 +855,7 @@ export function FileUpload({ component, onUpdateComponent, onFileUpload, userInf
variant="ghost"
size="sm"
onClick={() => previewFile(fileInfo)}
className="hover:bg-accent hover:text-primary h-9 w-9 rounded-lg transition-all duration-200"
className="h-8 w-8"
>
<Eye className="h-4 w-4" />
</Button>
@ -897,7 +865,7 @@ export function FileUpload({ component, onUpdateComponent, onFileUpload, userInf
variant="ghost"
size="sm"
onClick={() => previewFile(fileInfo)}
className="h-9 w-9 rounded-lg transition-all duration-200 hover:bg-emerald-50 hover:text-emerald-600"
className="h-8 w-8"
>
<Download className="h-4 w-4" />
</Button>
@ -906,7 +874,7 @@ export function FileUpload({ component, onUpdateComponent, onFileUpload, userInf
variant="ghost"
size="sm"
onClick={() => deleteFile(fileInfo)}
className="hover:bg-destructive/10 h-9 w-9 rounded-lg text-destructive transition-all duration-200 hover:text-destructive"
className="h-8 w-8 text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<X className="h-4 w-4" />
</Button>
@ -920,16 +888,14 @@ export function FileUpload({ component, onUpdateComponent, onFileUpload, userInf
)}
{/* 문서 타입 정보 */}
<div className="flex items-center justify-center space-x-2 rounded-xl border border-amber-200/40 bg-gradient-to-r from-amber-50/80 to-orange-50/60 px-4 py-3">
<div className="flex items-center justify-between rounded-md border border-border bg-muted/20 px-4 py-2.5">
<div className="flex items-center space-x-2">
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-amber-100">
<File className="h-3 w-3 text-amber-600" />
</div>
<span className="text-sm font-medium text-amber-700">
<File className="h-4 w-4 text-muted-foreground" />
<span className="text-xs text-muted-foreground">
"전체 자세히보기"
</span>
</div>
<Badge variant="outline" className="border-amber-200/60 bg-white/80 text-amber-700">
<Badge variant="outline">
{fileConfig.docTypeName}
</Badge>
</div>

View File

@ -134,22 +134,17 @@ export const FileWidget: React.FC<WebTypeComponentProps> = ({ component, value,
<div className="h-full w-full space-y-2">
{/* 파일 업로드 영역 */}
<div
className="group relative cursor-pointer rounded-2xl border-2 border-dashed border-input/60 bg-gradient-to-br from-muted/80 to-primary/5/40 p-8 text-center transition-all duration-300 hover:border-primary/60/80 hover:bg-gradient-to-br hover:from-primary/5/90 hover:to-indigo-50/60 hover:shadow-lg hover:shadow-blue-500/10"
className="group cursor-pointer rounded-lg border-2 border-dashed border-border bg-muted/20 p-6 text-center transition-colors duration-150 hover:border-primary/60 hover:bg-muted/30"
onClick={handleFileSelect}
onDragOver={handleDragOver}
onDrop={handleDrop}
style={style}
>
<div className="relative">
<Upload className="mx-auto mb-3 h-12 w-12 text-primary/80 transition-all duration-300 group-hover:scale-110 group-hover:text-primary" />
<div className="absolute inset-0 flex items-center justify-center">
<div className="h-16 w-16 rounded-full bg-primary/20/50 opacity-0 transition-all duration-300 group-hover:opacity-100 group-hover:scale-110"></div>
</div>
</div>
<p className="mb-2 text-lg font-semibold text-foreground transition-colors duration-300 group-hover:text-primary">
<Upload className="mx-auto mb-2 h-8 w-8 text-muted-foreground transition-colors duration-150 group-hover:text-primary" />
<p className="mb-1 text-sm font-medium text-foreground">
{readonly ? "파일 업로드 불가" : "파일을 선택하거나 드래그하여 업로드"}
</p>
<p className="text-sm text-muted-foreground transition-colors duration-300 group-hover:text-primary">
<p className="text-xs text-muted-foreground">
{config?.accept && `허용 형식: ${config.accept}`}
{config?.maxSize && ` (최대 ${config.maxSize}MB)`}
</p>
@ -167,39 +162,32 @@ export const FileWidget: React.FC<WebTypeComponentProps> = ({ component, value,
{/* 업로드된 파일 목록 */}
{files.length > 0 && (
<div className="space-y-3">
<div className="flex items-center justify-between rounded-xl bg-gradient-to-r from-primary/5/80 to-indigo-50/60 px-4 py-3 border border-primary/20/40">
<div className="flex items-center space-x-3">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/20">
<File className="h-4 w-4 text-primary" />
</div>
<div>
<h4 className="text-lg font-semibold text-foreground">
({files.length}/{config?.maxFiles || "∞"})
</h4>
<p className="text-sm text-muted-foreground">
{formatFileSize(files.reduce((sum, file) => sum + file.size, 0))}
</p>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between rounded-md border border-border bg-muted/20 px-3 py-2">
<div className="flex items-center space-x-2">
<File className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium text-foreground">
({files.length}/{config?.maxFiles || "∞"})
</span>
<span className="text-xs text-muted-foreground">
· {formatFileSize(files.reduce((sum, file) => sum + file.size, 0))}
</span>
</div>
</div>
<div className="max-h-40 space-y-2 overflow-y-auto">
<div className="max-h-40 space-y-1.5 overflow-y-auto">
{files.map((file, index) => (
<div key={index} className="group flex items-center justify-between rounded-xl border border-border/60 bg-white/90 p-3 shadow-sm transition-all duration-200 hover:shadow-md hover:border-primary/40/60 hover:bg-accent/30">
<div className="flex flex-1 items-center space-x-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-muted to-muted/80 shadow-sm">
<File className="h-5 w-5 text-muted-foreground" />
<div key={index} className="flex items-center justify-between rounded-md border border-border bg-background p-2.5 transition-colors duration-150 hover:bg-accent/30">
<div className="flex flex-1 items-center space-x-2">
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-muted">
<File className="h-4 w-4 text-muted-foreground" />
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold text-foreground group-hover:text-primary transition-colors duration-200">
<p className="truncate text-sm font-medium text-foreground">
{file.name}
</p>
{file.size > 0 && (
<div className="flex items-center space-x-2 mt-1">
<div className="h-1.5 w-1.5 rounded-full bg-primary/70"></div>
<span className="text-xs font-medium text-muted-foreground">{formatFileSize(file.size)}</span>
</div>
<span className="text-xs text-muted-foreground">{formatFileSize(file.size)}</span>
)}
</div>
</div>
@ -212,9 +200,9 @@ export const FileWidget: React.FC<WebTypeComponentProps> = ({ component, value,
e.stopPropagation();
removeFile(index);
}}
className="h-8 w-8 rounded-lg text-destructive hover:bg-destructive/10 hover:text-destructive transition-all duration-200"
className="h-7 w-7 text-destructive hover:bg-destructive/10 hover:text-destructive"
>
<X className="h-4 w-4" />
<X className="h-3.5 w-3.5" />
</Button>
)}
</div>
@ -225,21 +213,17 @@ export const FileWidget: React.FC<WebTypeComponentProps> = ({ component, value,
{/* 파일 개수 표시 */}
{files.length > 0 && (
<div className="flex items-center justify-center space-x-2 rounded-xl bg-gradient-to-r from-amber-50/80 to-orange-50/60 border border-amber-200/40 px-4 py-3">
<div className="flex items-center space-x-2">
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-amber-100">
<File className="h-3 w-3 text-amber-600" />
</div>
<span className="text-sm font-medium text-amber-700">
"전체 자세히보기"
<div className="flex items-center justify-between rounded-md border border-border bg-muted/20 px-3 py-2">
<div className="flex items-center space-x-1.5">
<File className="h-3.5 w-3.5 text-muted-foreground" />
<span className="text-xs text-muted-foreground">
</span>
</div>
<div className="flex items-center space-x-2">
<Badge variant="outline" className="bg-white/80 border-amber-200/60 text-amber-700">
{files.length}
</Badge>
<div className="flex items-center space-x-1.5">
<Badge variant="outline">{files.length} </Badge>
{config?.maxFiles && (
<Badge variant="outline" className="bg-white/80 border-border/60 text-muted-foreground">
<Badge variant="outline" className="text-muted-foreground">
{config.maxFiles}
</Badge>
)}

View File

@ -565,7 +565,7 @@ export function TaxInvoiceDetail({ open, onClose, invoiceId }: TaxInvoiceDetailP
(e.target as HTMLImageElement).style.display = "none";
}}
/>
<div className="absolute inset-0 flex items-end bg-gradient-to-t from-black/60 to-transparent opacity-0 transition-opacity group-hover:opacity-100">
<div className="absolute inset-0 flex items-end bg-black/50 opacity-0 transition-opacity group-hover:opacity-100">
<div className="w-full p-2">
<p className="truncate text-xs text-white">{file.file_name}</p>
<Button

21
run-e2e-spec-test.sh Normal file
View File

@ -0,0 +1,21 @@
#!/bin/bash
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/.nvm/versions/node/$(ls $HOME/.nvm/versions/node/ 2>/dev/null | tail -1)/bin"
cd /Users/gbpark/ERP-node
# Node 경로 찾기
NODE_BIN=""
if command -v node &>/dev/null; then
NODE_BIN=$(command -v node)
elif [ -f "$HOME/.nvm/nvm.sh" ]; then
source "$HOME/.nvm/nvm.sh"
NODE_BIN=$(command -v node)
fi
if [ -z "$NODE_BIN" ]; then
echo "RESULT: FAIL - node not found"
exit 1
fi
echo "Using node: $NODE_BIN"
$NODE_BIN /Users/gbpark/ERP-node/run-e2e-runtime-test.mjs