[agent-pipeline] pipe-20260317063830-0nfs round-2
This commit is contained in:
parent
128872b766
commit
265f46f8d4
|
|
@ -478,7 +478,7 @@ const DateConfigPanel: React.FC<DateConfigPanelProps> = ({
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
{sourceTableName && columns.length === 0 && !loadingColumns && (
|
{sourceTableName && columns.length === 0 && !loadingColumns && (
|
||||||
<p className="mt-1 text-[10px] text-amber-600 sm:text-xs">
|
<p className="mt-1 text-[10px] text-warning sm:text-xs">
|
||||||
이 테이블에 날짜 타입 컬럼이 없습니다
|
이 테이블에 날짜 타입 컬럼이 없습니다
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
|
@ -27,25 +26,24 @@ export const NumberingRuleCard: React.FC<NumberingRuleCardProps> = ({
|
||||||
tableName,
|
tableName,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Card className="border-border bg-card flex-1">
|
<div className="config-field flex-1 rounded-lg border border-border bg-muted/50 p-3 sm:p-4">
|
||||||
<CardHeader className="pb-3">
|
<div className="mb-3 flex items-center justify-between sm:mb-4">
|
||||||
<div className="flex items-center justify-between">
|
<Badge variant="outline" className="text-xs sm:text-sm">
|
||||||
<Badge variant="outline" className="text-xs sm:text-sm">
|
규칙 {part.order}
|
||||||
규칙 {part.order}
|
</Badge>
|
||||||
</Badge>
|
<Button
|
||||||
<Button
|
variant="destructive"
|
||||||
variant="ghost"
|
size="icon"
|
||||||
size="icon"
|
onClick={onDelete}
|
||||||
onClick={onDelete}
|
className="h-7 w-7 sm:h-8 sm:w-8"
|
||||||
className="text-destructive h-7 w-7 sm:h-8 sm:w-8"
|
disabled={isPreview}
|
||||||
disabled={isPreview}
|
aria-label="규칙 삭제"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3 sm:h-4 sm:w-4" />
|
<Trash2 className="h-3 w-3 sm:h-4 sm:w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<CardContent className="space-y-3 sm:space-y-4">
|
<div className="space-y-3 sm:space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-xs font-medium sm:text-sm">구분 유형</Label>
|
<Label className="text-xs font-medium sm:text-sm">구분 유형</Label>
|
||||||
<Select
|
<Select
|
||||||
|
|
@ -117,7 +115,7 @@ export const NumberingRuleCard: React.FC<NumberingRuleCardProps> = ({
|
||||||
isPreview={isPreview}
|
isPreview={isPreview}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -264,17 +264,18 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
const partItems = currentRule ? computePartDisplayItems(currentRule) : [];
|
const partItems = currentRule ? computePartDisplayItems(currentRule) : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex h-full gap-4", className)}>
|
<div className={cn("flex h-full", className)}>
|
||||||
{/* 좌측: 규칙 리스트 (code-nav, 220px) */}
|
{/* 좌측: 규칙 리스트 (code-nav, 220px) */}
|
||||||
<div className="code-nav flex w-[220px] flex-shrink-0 flex-col gap-3">
|
<div className="code-nav flex w-[220px] flex-shrink-0 flex-col border-r border-border">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="code-nav-head flex items-center justify-between gap-2 border-b border-border px-3 py-2.5">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||||
<ListOrdered className="h-4 w-4 text-muted-foreground" />
|
<ListOrdered className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||||
<span className="text-sm font-semibold">채번 규칙 ({rulesList.length})</span>
|
<span className="truncate text-xs font-bold">채번 규칙 ({rulesList.length})</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-8 gap-1 text-xs font-medium"
|
variant="default"
|
||||||
|
className="h-8 shrink-0 gap-1 text-xs font-medium"
|
||||||
onClick={handleAddNewRule}
|
onClick={handleAddNewRule}
|
||||||
disabled={isPreview || loading}
|
disabled={isPreview || loading}
|
||||||
>
|
>
|
||||||
|
|
@ -282,7 +283,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
추가
|
추가
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 space-y-0.5 overflow-y-auto">
|
<div className="code-nav-list flex-1 overflow-y-auto">
|
||||||
{loading && rulesList.length === 0 ? (
|
{loading && rulesList.length === 0 ? (
|
||||||
<div className="flex h-24 items-center justify-center text-xs text-muted-foreground">
|
<div className="flex h-24 items-center justify-center text-xs text-muted-foreground">
|
||||||
로딩 중...
|
로딩 중...
|
||||||
|
|
@ -299,21 +300,21 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
key={rule.ruleId}
|
key={rule.ruleId}
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"code-nav-item flex w-full flex-col items-start gap-0.5 rounded-md px-3 py-2 text-left transition-colors",
|
"code-nav-item flex w-full items-center gap-2 border-b border-border/50 px-3 py-2 text-left transition-colors",
|
||||||
isSelected
|
isSelected
|
||||||
? "border-l-[3px] border-primary bg-primary/5 font-bold"
|
? "border-l-[3px] border-primary bg-primary/5 pl-2.5 font-bold"
|
||||||
: "hover:bg-accent"
|
: "hover:bg-accent"
|
||||||
)}
|
)}
|
||||||
onClick={() => handleSelectRule(rule)}
|
onClick={() => handleSelectRule(rule)}
|
||||||
>
|
>
|
||||||
<span className="rule-name min-w-0 truncate text-xs">{rule.ruleName}</span>
|
<span className="rule-name min-w-0 flex-1 truncate text-xs font-semibold">
|
||||||
<span className="rule-table text-[9px] text-muted-foreground">
|
{rule.ruleName}
|
||||||
|
</span>
|
||||||
|
<span className="rule-table max-w-[70px] shrink-0 truncate text-[9px] text-muted-foreground">
|
||||||
{rule.tableName || "-"}
|
{rule.tableName || "-"}
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-0.5 inline-flex">
|
<span className="rule-parts shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-[8px] font-bold text-muted-foreground">
|
||||||
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
|
{rule.parts?.length ?? 0}
|
||||||
{rule.parts?.length ?? 0}개
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
@ -322,10 +323,8 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-full w-px flex-shrink-0 bg-border" />
|
{/* 우측: 미리보기 + 파이프라인 + 설정 + 저장 바 (code-main) */}
|
||||||
|
<div className="code-main flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||||
{/* 우측: 미리보기 + 파이프라인 + 설정 + 저장 바 */}
|
|
||||||
<div className="flex flex-1 flex-col gap-4 overflow-hidden">
|
|
||||||
{!currentRule ? (
|
{!currentRule ? (
|
||||||
<div className="flex flex-1 flex-col items-center justify-center text-center">
|
<div className="flex flex-1 flex-col items-center justify-center text-center">
|
||||||
<ListOrdered className="mb-3 h-10 w-10 text-muted-foreground" />
|
<ListOrdered className="mb-3 h-10 w-10 text-muted-foreground" />
|
||||||
|
|
@ -336,7 +335,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2 px-6 pt-4">
|
||||||
<Label className="text-xs font-medium">규칙명</Label>
|
<Label className="text-xs font-medium">규칙명</Label>
|
||||||
<Input
|
<Input
|
||||||
value={currentRule.ruleName}
|
value={currentRule.ruleName}
|
||||||
|
|
@ -347,19 +346,19 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 큰 미리보기 스트립 (code-preview-strip) */}
|
{/* 큰 미리보기 스트립 (code-preview-strip) */}
|
||||||
<div className="code-preview-strip flex-shrink-0">
|
<div className="code-preview-strip flex-shrink-0 border-b border-border px-6 py-5">
|
||||||
<NumberingRulePreview config={currentRule} variant="strip" />
|
<NumberingRulePreview config={currentRule} variant="strip" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 파이프라인 영역 (code-pipeline-area) */}
|
{/* 파이프라인 영역 (code-pipeline-area) */}
|
||||||
<div className="code-pipeline-area flex flex-col gap-2">
|
<div className="code-pipeline-area flex flex-col gap-3 border-b border-border px-6 py-5">
|
||||||
<div className="flex items-center justify-between">
|
<div className="area-label flex items-center gap-1.5">
|
||||||
<span className="text-xs font-semibold text-muted-foreground">코드 구성</span>
|
<span className="text-xs font-bold">코드 구성</span>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="cnt text-xs font-medium text-muted-foreground">
|
||||||
{currentRule.parts.length}/{maxRules}
|
{currentRule.parts.length}/{maxRules}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 flex-wrap items-center gap-2 overflow-x-auto overflow-y-hidden py-1">
|
<div className="code-pipeline flex flex-1 flex-wrap items-center gap-0 overflow-x-auto overflow-y-hidden pb-2">
|
||||||
{currentRule.parts.length === 0 ? (
|
{currentRule.parts.length === 0 ? (
|
||||||
<div className="flex h-24 min-w-[200px] items-center justify-center rounded-xl border-2 border-dashed border-border bg-muted/30 text-xs text-muted-foreground">
|
<div className="flex h-24 min-w-[200px] items-center justify-center rounded-xl border-2 border-dashed border-border bg-muted/30 text-xs text-muted-foreground">
|
||||||
규칙을 추가하여 코드를 구성하세요
|
규칙을 추가하여 코드를 구성하세요
|
||||||
|
|
@ -376,7 +375,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"pipe-segment min-w-[120px] rounded-[10px] border-2 px-3 py-3 text-left transition-all",
|
"pipe-segment min-w-[120px] flex-shrink-0 rounded-[10px] border-2 px-4 py-3 text-center transition-all",
|
||||||
part.partType === "date" && "border-warning",
|
part.partType === "date" && "border-warning",
|
||||||
part.partType === "text" && "border-primary",
|
part.partType === "text" && "border-primary",
|
||||||
part.partType === "sequence" && "border-primary",
|
part.partType === "sequence" && "border-primary",
|
||||||
|
|
@ -385,25 +384,27 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
)}
|
)}
|
||||||
onClick={() => setSelectedPartOrder(part.order)}
|
onClick={() => setSelectedPartOrder(part.order)}
|
||||||
>
|
>
|
||||||
<div className="text-[10px] font-medium text-muted-foreground">{typeLabel}</div>
|
<div className="seg-type text-[8px] font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
<div className={cn("mt-0.5 truncate font-mono text-sm font-medium", getPartTypeColorClass(part.partType))}>
|
{typeLabel}
|
||||||
|
</div>
|
||||||
|
<div className={cn("seg-value mt-1 truncate font-mono text-base font-extrabold leading-none", getPartTypeColorClass(part.partType))}>
|
||||||
{item?.displayValue ?? "-"}
|
{item?.displayValue ?? "-"}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{index < currentRule.parts.length - 1 && (
|
{index < currentRule.parts.length - 1 && (
|
||||||
<span className="flex items-center gap-1 text-muted-foreground">
|
<div className="pipe-connector flex w-8 flex-shrink-0 flex-col items-center justify-center gap-0.5">
|
||||||
<span className="text-xs">→</span>
|
<span className="conn-line text-xs font-bold text-muted-foreground">→</span>
|
||||||
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] font-mono">
|
<span className="conn-sep rounded border border-border bg-muted px-1 py-0.5 text-[8px] font-semibold text-muted-foreground">
|
||||||
{sep || "(없음)"}
|
{sep || "-"}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex h-[52px] w-10 flex-shrink-0 items-center justify-center rounded-full border-2 border-dashed border-border text-muted-foreground transition-colors hover:border-primary hover:bg-primary/5 hover:text-primary"
|
className="pipe-add flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px] border-2 border-dashed border-border text-muted-foreground transition-colors hover:border-primary hover:bg-primary/5 hover:text-primary"
|
||||||
onClick={handleAddPart}
|
onClick={handleAddPart}
|
||||||
disabled={currentRule.parts.length >= maxRules || isPreview || loading}
|
disabled={currentRule.parts.length >= maxRules || isPreview || loading}
|
||||||
aria-label="규칙 추가"
|
aria-label="규칙 추가"
|
||||||
|
|
@ -417,8 +418,8 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
|
|
||||||
{/* 설정 패널 (선택된 세그먼트 상세, code-config-panel) */}
|
{/* 설정 패널 (선택된 세그먼트 상세, code-config-panel) */}
|
||||||
{selectedPart && (
|
{selectedPart && (
|
||||||
<div className="code-config-panel min-h-0 flex-1 overflow-y-auto rounded-lg bg-muted/30 p-4">
|
<div className="code-config-panel min-h-0 flex-1 overflow-y-auto px-6 py-5">
|
||||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-3">
|
<div className="code-config-grid grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-3">
|
||||||
<NumberingRuleCard
|
<NumberingRuleCard
|
||||||
part={selectedPart}
|
part={selectedPart}
|
||||||
onUpdate={(updates) => handleUpdatePart(selectedPart.order, updates)}
|
onUpdate={(updates) => handleUpdatePart(selectedPart.order, updates)}
|
||||||
|
|
@ -460,7 +461,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 저장 바 (code-save-bar) */}
|
{/* 저장 바 (code-save-bar) */}
|
||||||
<div className="code-save-bar flex flex-shrink-0 items-center justify-between gap-4 border-t border-border pt-4">
|
<div className="code-save-bar flex flex-shrink-0 items-center justify-between gap-4 border-t border-border bg-muted/30 px-6 py-4">
|
||||||
<div className="min-w-0 flex-1 text-xs text-muted-foreground">
|
<div className="min-w-0 flex-1 text-xs text-muted-foreground">
|
||||||
{currentRule.tableName && (
|
{currentRule.tableName && (
|
||||||
<span>테이블: {currentRule.tableName}</span>
|
<span>테이블: {currentRule.tableName}</span>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
import { NumberingRuleConfig, NumberingRulePart, CodePartType } from "@/types/numbering-rule";
|
import { NumberingRuleConfig, NumberingRulePart, CodePartType } from "@/types/numbering-rule";
|
||||||
import { CODE_PART_TYPE_OPTIONS } from "@/types/numbering-rule";
|
import { CODE_PART_TYPE_OPTIONS } from "@/types/numbering-rule";
|
||||||
|
|
||||||
|
|
@ -127,8 +128,8 @@ export const NumberingRulePreview: React.FC<NumberingRulePreviewProps> = ({
|
||||||
if (variant === "strip") {
|
if (variant === "strip") {
|
||||||
const globalSep = config.separator ?? "-";
|
const globalSep = config.separator ?? "-";
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg bg-gradient-to-b from-muted to-card px-4 py-4">
|
<div className="rounded-lg bg-gradient-to-b from-muted to-card px-4 py-4 sm:px-6 sm:py-5">
|
||||||
<div className="font-mono text-[28px] font-extrabold tracking-tight">
|
<div className="font-mono text-[22px] font-extrabold tracking-tight sm:text-[28px]">
|
||||||
{partItems.length === 0 ? (
|
{partItems.length === 0 ? (
|
||||||
<span className="text-muted-foreground">규칙을 추가해주세요</span>
|
<span className="text-muted-foreground">규칙을 추가해주세요</span>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -145,10 +146,10 @@ export const NumberingRulePreview: React.FC<NumberingRulePreviewProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{partItems.length > 0 && (
|
{partItems.length > 0 && (
|
||||||
<div className="mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
|
<div className="preview-desc mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
|
||||||
{CODE_PART_TYPE_OPTIONS.filter((opt) => partItems.some((p) => p.partType === opt.value)).map((opt) => (
|
{CODE_PART_TYPE_OPTIONS.filter((opt) => partItems.some((p) => p.partType === opt.value)).map((opt) => (
|
||||||
<span key={opt.value} className="flex items-center gap-1.5">
|
<span key={opt.value} className="flex items-center gap-1.5">
|
||||||
<span className={`h-1.5 w-1.5 rounded-full ${getPartTypeDotClass(opt.value)}`} />
|
<span className={cn("h-[6px] w-[6px] shrink-0 rounded-full", getPartTypeDotClass(opt.value))} />
|
||||||
{opt.label}
|
{opt.label}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ export const CategoryValueAddDialog: React.FC<
|
||||||
<div className="space-y-3 sm:space-y-4">
|
<div className="space-y-3 sm:space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="valueLabel" className="text-xs sm:text-sm">
|
<Label htmlFor="valueLabel" className="text-xs sm:text-sm">
|
||||||
이름
|
이름 <span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="valueLabel"
|
id="valueLabel"
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ export const CategoryValueEditDialog: React.FC<
|
||||||
<div className="space-y-3 sm:space-y-4">
|
<div className="space-y-3 sm:space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="valueLabel" className="text-xs sm:text-sm">
|
<Label htmlFor="valueLabel" className="text-xs sm:text-sm">
|
||||||
이름
|
이름 <span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="valueLabel"
|
id="valueLabel"
|
||||||
|
|
|
||||||
|
|
@ -405,7 +405,6 @@ export const CategoryValueManager: React.FC<CategoryValueManagerProps> = ({
|
||||||
value.isActive !== false
|
value.isActive !== false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="data-[state=checked]:bg-emerald-500"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import {
|
||||||
Search,
|
Search,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
|
@ -144,7 +145,7 @@ const TreeNode: React.FC<TreeNodeProps> = ({
|
||||||
<div className="mb-px">
|
<div className="mb-px">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex cursor-pointer items-center gap-[5px] rounded-[6px] px-2 py-[5px] transition-colors",
|
"group flex cursor-pointer items-center gap-[5px] rounded-[6px] px-[8px] py-[5px] transition-colors",
|
||||||
isSelected ? "border-primary border-l-2 bg-primary/10" : "hover:bg-muted/50",
|
isSelected ? "border-primary border-l-2 bg-primary/10" : "hover:bg-muted/50",
|
||||||
isChecked && "bg-primary/5",
|
isChecked && "bg-primary/5",
|
||||||
)}
|
)}
|
||||||
|
|
@ -633,10 +634,13 @@ export const CategoryValueManagerTree: React.FC<CategoryValueManagerTreeProps> =
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
{/* 헤더 */}
|
{/* 편집기 헤더: 컬럼명 + 값 수 Badge + 선택 Badge + 액션 버튼 */}
|
||||||
<div className="mb-3 flex items-center justify-between border-b pb-3">
|
<div className="mb-3 flex items-center justify-between border-b pb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="text-base font-semibold">{columnLabel} 카테고리</h3>
|
<h3 className="text-base font-semibold">{columnLabel} 카테고리</h3>
|
||||||
|
<Badge variant="secondary" className="rounded-full px-2 py-0.5 text-xs font-bold">
|
||||||
|
{countAllValues(tree)}개
|
||||||
|
</Badge>
|
||||||
{checkedIds.size > 0 && (
|
{checkedIds.size > 0 && (
|
||||||
<span className="bg-primary/10 text-primary rounded-full px-2 py-0.5 text-xs">
|
<span className="bg-primary/10 text-primary rounded-full px-2 py-0.5 text-xs">
|
||||||
{checkedIds.size}개 선택
|
{checkedIds.size}개 선택
|
||||||
|
|
@ -719,7 +723,7 @@ export const CategoryValueManagerTree: React.FC<CategoryValueManagerTreeProps> =
|
||||||
<p className="text-muted-foreground mt-1 text-xs">상단의 대분류 추가 버튼을 클릭하여 시작하세요</p>
|
<p className="text-muted-foreground mt-1 text-xs">상단의 대분류 추가 버튼을 클릭하여 시작하세요</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-2">
|
<div className="py-1">
|
||||||
{tree.map((node) => (
|
{tree.map((node) => (
|
||||||
<TreeNode
|
<TreeNode
|
||||||
key={node.valueId}
|
key={node.valueId}
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ export function V2CategoryManagerComponent({
|
||||||
className="flex h-full flex-col overflow-hidden rounded-lg border bg-card text-card-foreground shadow-sm"
|
className="flex h-full flex-col overflow-hidden rounded-lg border bg-card text-card-foreground shadow-sm"
|
||||||
style={{ height: config.height }}
|
style={{ height: config.height }}
|
||||||
>
|
>
|
||||||
{/* Stat Strip */}
|
{/* Stat Strip: 카테고리 컬럼(primary) | 전체 값(success) | 테이블(primary) | 비활성(warning) */}
|
||||||
<div className="grid grid-cols-4 border-b bg-background">
|
<div className="grid grid-cols-4 border-b bg-background">
|
||||||
<div className="border-r py-3.5 text-center last:border-r-0">
|
<div className="border-r py-3.5 text-center last:border-r-0">
|
||||||
<div className="text-[22px] font-extrabold leading-none tracking-tight text-primary">
|
<div className="text-[22px] font-extrabold leading-none tracking-tight text-primary">
|
||||||
|
|
@ -173,7 +173,7 @@ export function V2CategoryManagerComponent({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-r py-3.5 text-center last:border-r-0">
|
<div className="border-r py-3.5 text-center last:border-r-0">
|
||||||
<div className="text-[22px] font-extrabold leading-none tracking-tight text-primary">
|
<div className="text-[22px] font-extrabold leading-none tracking-tight text-success">
|
||||||
{stats.totalValues}
|
{stats.totalValues}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-[9px] font-semibold uppercase tracking-widest text-muted-foreground">
|
<div className="mt-1 text-[9px] font-semibold uppercase tracking-widest text-muted-foreground">
|
||||||
|
|
@ -189,7 +189,7 @@ export function V2CategoryManagerComponent({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-3.5 text-center">
|
<div className="py-3.5 text-center">
|
||||||
<div className="text-[22px] font-extrabold leading-none tracking-tight text-primary">
|
<div className="text-[22px] font-extrabold leading-none tracking-tight text-warning">
|
||||||
{stats.inactiveCount}
|
{stats.inactiveCount}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-[9px] font-semibold uppercase tracking-widest text-muted-foreground">
|
<div className="mt-1 text-[9px] font-semibold uppercase tracking-widest text-muted-foreground">
|
||||||
|
|
@ -227,7 +227,7 @@ export function V2CategoryManagerComponent({
|
||||||
handleColumnSelect(uniqueKey, col.columnLabel || col.columnName, col.tableName)
|
handleColumnSelect(uniqueKey, col.columnLabel || col.columnName, col.tableName)
|
||||||
}
|
}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1.5 text-[11px] font-semibold transition-colors",
|
"inline-flex items-center gap-1.5 rounded-full border px-[10px] py-[5px] text-[11px] font-semibold transition-colors",
|
||||||
isActive
|
isActive
|
||||||
? "border-primary bg-primary/5 text-primary"
|
? "border-primary bg-primary/5 text-primary"
|
||||||
: "border-border bg-muted/50 hover:border-primary hover:bg-primary/5 hover:text-primary",
|
: "border-border bg-muted/50 hover:border-primary hover:bg-primary/5 hover:text-primary",
|
||||||
|
|
|
||||||
|
|
@ -839,7 +839,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
height: "100%",
|
height: "100%",
|
||||||
minHeight: getHeightValue(),
|
minHeight: getHeightValue(),
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
border: isSelected ? "2px solid #3b82f6" : "1px solid #e5e7eb",
|
border: isSelected ? "2px solid hsl(var(--primary))" : "1px solid hsl(var(--border))",
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
|
@ -1040,12 +1040,12 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
const barWidth = Math.min(percentage, 100);
|
const barWidth = Math.min(percentage, 100);
|
||||||
const barColor =
|
const barColor =
|
||||||
percentage > 100
|
percentage > 100
|
||||||
? "bg-red-600"
|
? "bg-destructive"
|
||||||
: percentage >= 90
|
: percentage >= 90
|
||||||
? "bg-red-500"
|
? "bg-destructive"
|
||||||
: percentage >= 70
|
: percentage >= 70
|
||||||
? "bg-amber-500"
|
? "bg-warning"
|
||||||
: "bg-emerald-500";
|
: "bg-success";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-w-[120px] items-center gap-2">
|
<div className="flex min-w-[120px] items-center gap-2">
|
||||||
|
|
|
||||||
|
|
@ -367,8 +367,9 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
|
||||||
const hasCategoryOptions =
|
const hasCategoryOptions =
|
||||||
isCategoryType && categoryOptions && Object.keys(categoryOptions).length > 0;
|
isCategoryType && categoryOptions && Object.keys(categoryOptions).length > 0;
|
||||||
|
|
||||||
|
// 인라인 편집: 행 높이 유지를 위해 select/input 모두 h-8(32px) 고정
|
||||||
const commonInputClass =
|
const commonInputClass =
|
||||||
"border-primary bg-background focus:ring-primary h-8 w-full rounded border px-2 text-xs focus:ring-2 focus:outline-none sm:text-sm";
|
"border-primary bg-background focus:ring-primary h-8 w-full shrink-0 rounded border px-2 text-xs focus:ring-2 focus:outline-none sm:text-sm";
|
||||||
const handleBlurSave = () => {
|
const handleBlurSave = () => {
|
||||||
if (onEditKeyDown) {
|
if (onEditKeyDown) {
|
||||||
const fakeEvent = {
|
const fakeEvent = {
|
||||||
|
|
|
||||||
|
|
@ -6419,7 +6419,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
onChange={(e) => setEditingValue(e.target.value)}
|
onChange={(e) => setEditingValue(e.target.value)}
|
||||||
onKeyDown={handleEditKeyDown}
|
onKeyDown={handleEditKeyDown}
|
||||||
onBlur={saveEditing}
|
onBlur={saveEditing}
|
||||||
className="border-primary bg-background h-8 w-full border-2 px-2 py-1 text-xs focus:outline-none sm:px-4 sm:py-1.5 sm:text-sm"
|
className="border-primary bg-background h-8 w-full shrink-0 border-2 px-2 py-1 text-xs focus:outline-none sm:px-4 sm:py-1.5 sm:text-sm"
|
||||||
autoFocus
|
autoFocus
|
||||||
>
|
>
|
||||||
<option value="">선택하세요</option>
|
<option value="">선택하세요</option>
|
||||||
|
|
@ -6447,7 +6447,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 일반 입력 필드
|
// 일반 입력 필드 (행 높이 유지: h-8 고정)
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
ref={editInputRef}
|
ref={editInputRef}
|
||||||
|
|
@ -6456,7 +6456,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
onChange={(e) => setEditingValue(e.target.value)}
|
onChange={(e) => setEditingValue(e.target.value)}
|
||||||
onKeyDown={handleEditKeyDown}
|
onKeyDown={handleEditKeyDown}
|
||||||
onBlur={saveEditing}
|
onBlur={saveEditing}
|
||||||
className="border-primary bg-background h-8 w-full border-2 px-2 py-1 text-xs focus:outline-none sm:px-4 sm:py-1.5 sm:text-sm"
|
className="border-primary bg-background h-8 w-full shrink-0 border-2 px-2 py-1 text-xs focus:outline-none sm:px-4 sm:py-1.5 sm:text-sm"
|
||||||
style={{
|
style={{
|
||||||
textAlign: isNumeric ? "right" : column.align || "left",
|
textAlign: isNumeric ? "right" : column.align || "left",
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue