[agent-pipeline] pipe-20260309055714-23ry round-1
This commit is contained in:
parent
4f10b5e42d
commit
197ddf47cf
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
시각적 필드 매핑으로 데이터 연결을 쉽게 설정하세요
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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) => (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue