feat: 카테고리 배지 없음 옵션 추가
- 카테고리 색상 설정 시 '배지 없음' 옵션 추가 - color='none'일 때 배지 대신 일반 텍스트로 표시 - CategoryValueEditDialog, CategoryValueAddDialog에 배지 없음 버튼 추가 - InteractiveDataTable, TableListComponent에서 배지 없음 처리 - CategoryValueManager에서 배지 없음 표시 추가 - 기본 색상을 배지 없음으로 변경
This commit is contained in:
@@ -1961,7 +1961,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
|||||||
// 실제 웹 타입으로 스위치 (input_type="category"도 포함됨)
|
// 실제 웹 타입으로 스위치 (input_type="category"도 포함됨)
|
||||||
switch (actualWebType) {
|
switch (actualWebType) {
|
||||||
case "category": {
|
case "category": {
|
||||||
// 카테고리 타입: 배지로 표시
|
// 카테고리 타입: 배지로 표시 (배지 없음 옵션 지원)
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
|
|
||||||
const mapping = categoryMappings[column.columnName];
|
const mapping = categoryMappings[column.columnName];
|
||||||
@@ -1971,6 +1971,11 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
|||||||
const displayLabel = categoryData?.label || String(value);
|
const displayLabel = categoryData?.label || String(value);
|
||||||
const displayColor = categoryData?.color || "#64748b"; // 기본 slate 색상
|
const displayColor = categoryData?.color || "#64748b"; // 기본 slate 색상
|
||||||
|
|
||||||
|
// 배지 없음 옵션: color가 "none"이면 텍스트만 표시
|
||||||
|
if (displayColor === "none") {
|
||||||
|
return <span className="text-sm">{displayLabel}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const CategoryValueAddDialog: React.FC<
|
|||||||
> = ({ open, onOpenChange, onAdd, columnLabel }) => {
|
> = ({ open, onOpenChange, onAdd, columnLabel }) => {
|
||||||
const [valueLabel, setValueLabel] = useState("");
|
const [valueLabel, setValueLabel] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
const [color, setColor] = useState("#3b82f6");
|
const [color, setColor] = useState("none");
|
||||||
|
|
||||||
// 라벨에서 코드 자동 생성
|
// 라벨에서 코드 자동 생성
|
||||||
const generateCode = (label: string): string => {
|
const generateCode = (label: string): string => {
|
||||||
@@ -91,7 +91,7 @@ export const CategoryValueAddDialog: React.FC<
|
|||||||
// 초기화
|
// 초기화
|
||||||
setValueLabel("");
|
setValueLabel("");
|
||||||
setDescription("");
|
setDescription("");
|
||||||
setColor("#3b82f6");
|
setColor("none");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -123,24 +123,41 @@ export const CategoryValueAddDialog: React.FC<
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-xs sm:text-sm">배지 색상</Label>
|
<Label className="text-xs sm:text-sm">배지 색상</Label>
|
||||||
<div className="mt-1.5 flex items-center gap-3">
|
<div className="mt-1.5 space-y-2">
|
||||||
<div className="grid grid-cols-9 gap-2">
|
<div className="flex items-center gap-3">
|
||||||
{DEFAULT_COLORS.map((c) => (
|
<div className="grid grid-cols-9 gap-2">
|
||||||
<button
|
{DEFAULT_COLORS.map((c) => (
|
||||||
key={c}
|
<button
|
||||||
type="button"
|
key={c}
|
||||||
onClick={() => setColor(c)}
|
type="button"
|
||||||
className={`h-7 w-7 rounded-md border-2 transition-all ${
|
onClick={() => setColor(c)}
|
||||||
color === c ? "border-foreground scale-110" : "border-transparent hover:scale-105"
|
className={`h-7 w-7 rounded-md border-2 transition-all ${
|
||||||
}`}
|
color === c ? "border-foreground scale-110" : "border-transparent hover:scale-105"
|
||||||
style={{ backgroundColor: c }}
|
}`}
|
||||||
title={c}
|
style={{ backgroundColor: c }}
|
||||||
/>
|
title={c}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{color && color !== "none" ? (
|
||||||
|
<Badge style={{ backgroundColor: color, borderColor: color }} className="text-white">
|
||||||
|
미리보기
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground">배지 없음</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Badge style={{ backgroundColor: color, borderColor: color }} className="text-white">
|
<button
|
||||||
미리보기
|
type="button"
|
||||||
</Badge>
|
onClick={() => setColor("none")}
|
||||||
|
className={`text-xs px-3 py-1.5 rounded-md border transition-colors ${
|
||||||
|
color === "none"
|
||||||
|
? "border-primary bg-primary/10 text-primary font-medium"
|
||||||
|
: "border-border hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
배지 없음
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ export const CategoryValueEditDialog: React.FC<
|
|||||||
> = ({ open, onOpenChange, value, onUpdate, columnLabel }) => {
|
> = ({ open, onOpenChange, value, onUpdate, columnLabel }) => {
|
||||||
const [valueLabel, setValueLabel] = useState(value.valueLabel);
|
const [valueLabel, setValueLabel] = useState(value.valueLabel);
|
||||||
const [description, setDescription] = useState(value.description || "");
|
const [description, setDescription] = useState(value.description || "");
|
||||||
const [color, setColor] = useState(value.color || "#3b82f6");
|
const [color, setColor] = useState(value.color || "none");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValueLabel(value.valueLabel);
|
setValueLabel(value.valueLabel);
|
||||||
setDescription(value.description || "");
|
setDescription(value.description || "");
|
||||||
setColor(value.color || "#3b82f6");
|
setColor(value.color || "none");
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
@@ -100,24 +100,41 @@ export const CategoryValueEditDialog: React.FC<
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-xs sm:text-sm">배지 색상</Label>
|
<Label className="text-xs sm:text-sm">배지 색상</Label>
|
||||||
<div className="mt-1.5 flex items-center gap-3">
|
<div className="mt-1.5 space-y-2">
|
||||||
<div className="grid grid-cols-9 gap-2">
|
<div className="flex items-center gap-3">
|
||||||
{DEFAULT_COLORS.map((c) => (
|
<div className="grid grid-cols-9 gap-2">
|
||||||
<button
|
{DEFAULT_COLORS.map((c) => (
|
||||||
key={c}
|
<button
|
||||||
type="button"
|
key={c}
|
||||||
onClick={() => setColor(c)}
|
type="button"
|
||||||
className={`h-7 w-7 rounded-md border-2 transition-all ${
|
onClick={() => setColor(c)}
|
||||||
color === c ? "border-foreground scale-110" : "border-transparent hover:scale-105"
|
className={`h-7 w-7 rounded-md border-2 transition-all ${
|
||||||
}`}
|
color === c ? "border-foreground scale-110" : "border-transparent hover:scale-105"
|
||||||
style={{ backgroundColor: c }}
|
}`}
|
||||||
title={c}
|
style={{ backgroundColor: c }}
|
||||||
/>
|
title={c}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{color && color !== "none" ? (
|
||||||
|
<Badge style={{ backgroundColor: color, borderColor: color }} className="text-white">
|
||||||
|
미리보기
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground">배지 없음</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Badge style={{ backgroundColor: color, borderColor: color }} className="text-white">
|
<button
|
||||||
미리보기
|
type="button"
|
||||||
</Badge>
|
onClick={() => setColor("none")}
|
||||||
|
className={`text-xs px-3 py-1.5 rounded-md border transition-colors ${
|
||||||
|
color === "none"
|
||||||
|
? "border-primary bg-primary/10 text-primary font-medium"
|
||||||
|
: "border-border hover:bg-accent"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
배지 없음
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -349,13 +349,18 @@ export const CategoryValueManager: React.FC<CategoryValueManagerProps> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex flex-1 items-center gap-2">
|
<div className="flex flex-1 items-center gap-2">
|
||||||
{/* 색상 표시 (앞쪽으로 이동) */}
|
{/* 색상 표시 (배지 없음 옵션 지원) */}
|
||||||
{value.color && (
|
{value.color && value.color !== "none" && (
|
||||||
<div
|
<div
|
||||||
className="h-4 w-4 rounded-full border flex-shrink-0"
|
className="h-4 w-4 rounded-full border flex-shrink-0"
|
||||||
style={{ backgroundColor: value.color }}
|
style={{ backgroundColor: value.color }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{value.color === "none" && (
|
||||||
|
<span className="text-[10px] text-muted-foreground px-1.5 py-0.5 bg-muted rounded">
|
||||||
|
배지 없음
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 라벨 */}
|
{/* 라벨 */}
|
||||||
<span className={`text-sm font-medium ${isInactive ? "line-through" : ""}`}>
|
<span className={`text-sm font-medium ${isInactive ? "line-through" : ""}`}>
|
||||||
|
|||||||
@@ -1374,28 +1374,22 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 카테고리 타입: 배지로 표시
|
// 카테고리 타입: 배지로 표시 (배지 없음 옵션 지원)
|
||||||
if (inputType === "category") {
|
if (inputType === "category") {
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
|
|
||||||
const mapping = categoryMappings[column.columnName];
|
const mapping = categoryMappings[column.columnName];
|
||||||
const categoryData = mapping?.[String(value)];
|
const categoryData = mapping?.[String(value)];
|
||||||
|
|
||||||
// console.log(`🎨 [카테고리 배지] ${column.columnName}:`, {
|
|
||||||
// value,
|
|
||||||
// stringValue: String(value),
|
|
||||||
// mapping,
|
|
||||||
// categoryData,
|
|
||||||
// hasMapping: !!mapping,
|
|
||||||
// hasCategoryData: !!categoryData,
|
|
||||||
// allCategoryMappings: categoryMappings, // 전체 매핑 확인
|
|
||||||
// categoryMappingsKeys: Object.keys(categoryMappings),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// 매핑 데이터가 있으면 라벨과 색상 사용, 없으면 코드값과 기본색상
|
// 매핑 데이터가 있으면 라벨과 색상 사용, 없으면 코드값과 기본색상
|
||||||
const displayLabel = categoryData?.label || String(value);
|
const displayLabel = categoryData?.label || String(value);
|
||||||
const displayColor = categoryData?.color || "#64748b"; // 기본 slate 색상
|
const displayColor = categoryData?.color || "#64748b"; // 기본 slate 색상
|
||||||
|
|
||||||
|
// 배지 없음 옵션: color가 "none"이면 텍스트만 표시
|
||||||
|
if (displayColor === "none") {
|
||||||
|
return <span className="text-sm">{displayLabel}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
const { Badge } = require("@/components/ui/badge");
|
const { Badge } = require("@/components/ui/badge");
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
|
|||||||
Reference in New Issue
Block a user