feat: Introduce new date picker components for enhanced date selection
- Added `FormDatePicker` and `InlineCellDatePicker` components to provide flexible date selection options. - Implemented a modernized date picker interface with calendar navigation, year selection, and time input capabilities. - Enhanced `DateWidget` to support both date and datetime formats, improving user experience in date handling. - Updated `CategoryColumnList` to group columns dynamically and manage expanded states for better organization. - Improved `AlertDialog` z-index for better visibility in modal interactions. - Refactored `ScreenModal` to ensure consistent modal behavior across the application.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
import { getCategoryValues } from "@/lib/api/tableCategoryValue";
|
||||
import { FolderTree, Loader2, Search, X } from "lucide-react";
|
||||
import { ChevronRight, FolderTree, Loader2, Search, X } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
interface CategoryColumn {
|
||||
@@ -30,6 +30,7 @@ export function CategoryColumnList({ tableName, selectedColumn, onColumnSelect,
|
||||
const [columns, setColumns] = useState<CategoryColumn[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
||||
|
||||
// 검색어로 필터링된 컬럼 목록
|
||||
const filteredColumns = useMemo(() => {
|
||||
@@ -49,6 +50,44 @@ export function CategoryColumnList({ tableName, selectedColumn, onColumnSelect,
|
||||
});
|
||||
}, [columns, searchQuery]);
|
||||
|
||||
// 테이블별로 그룹화된 컬럼 목록
|
||||
const groupedColumns = useMemo(() => {
|
||||
const groups: { tableName: string; tableLabel: string; columns: CategoryColumn[] }[] = [];
|
||||
const groupMap = new Map<string, CategoryColumn[]>();
|
||||
|
||||
for (const col of filteredColumns) {
|
||||
const key = col.tableName;
|
||||
if (!groupMap.has(key)) {
|
||||
groupMap.set(key, []);
|
||||
}
|
||||
groupMap.get(key)!.push(col);
|
||||
}
|
||||
|
||||
for (const [tblName, cols] of groupMap) {
|
||||
groups.push({
|
||||
tableName: tblName,
|
||||
tableLabel: cols[0]?.tableLabel || tblName,
|
||||
columns: cols,
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
}, [filteredColumns]);
|
||||
|
||||
// 선택된 컬럼이 있는 그룹을 자동 펼침
|
||||
useEffect(() => {
|
||||
if (!selectedColumn) return;
|
||||
const tableName = selectedColumn.split(".")[0];
|
||||
if (tableName) {
|
||||
setExpandedGroups((prev) => {
|
||||
if (prev.has(tableName)) return prev;
|
||||
const next = new Set(prev);
|
||||
next.add(tableName);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
}, [selectedColumn]);
|
||||
|
||||
useEffect(() => {
|
||||
// 메뉴 종속 없이 항상 회사 기준으로 카테고리 컬럼 조회
|
||||
loadCategoryColumnsByMenu();
|
||||
@@ -279,35 +318,114 @@ export function CategoryColumnList({ tableName, selectedColumn, onColumnSelect,
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-1">
|
||||
{filteredColumns.length === 0 && searchQuery ? (
|
||||
<div className="text-muted-foreground py-4 text-center text-xs">
|
||||
'{searchQuery}'에 대한 검색 결과가 없습니다
|
||||
</div>
|
||||
) : null}
|
||||
{filteredColumns.map((column) => {
|
||||
const uniqueKey = `${column.tableName}.${column.columnName}`;
|
||||
const isSelected = selectedColumn === uniqueKey; // 테이블명.컬럼명으로 비교
|
||||
return (
|
||||
<div
|
||||
key={uniqueKey}
|
||||
onClick={() => onColumnSelect(uniqueKey, column.columnLabel || column.columnName, column.tableName)}
|
||||
className={`cursor-pointer rounded-lg border px-4 py-2 transition-all ${
|
||||
isSelected ? "border-primary bg-primary/10 shadow-sm" : "hover:bg-muted/50"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<FolderTree
|
||||
className={`h-4 w-4 ${isSelected ? "text-primary" : "text-muted-foreground"}`}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-semibold">{column.columnLabel || column.columnName}</h4>
|
||||
<p className="text-muted-foreground text-xs">{column.tableLabel || column.tableName}</p>
|
||||
</div>
|
||||
<span className="text-muted-foreground text-xs font-medium">
|
||||
{column.valueCount !== undefined ? `${column.valueCount}개` : "..."}
|
||||
</span>
|
||||
{groupedColumns.map((group) => {
|
||||
const isExpanded = expandedGroups.has(group.tableName);
|
||||
const totalValues = group.columns.reduce((sum, c) => sum + (c.valueCount ?? 0), 0);
|
||||
const hasSelectedInGroup = group.columns.some(
|
||||
(c) => selectedColumn === `${c.tableName}.${c.columnName}`,
|
||||
);
|
||||
|
||||
// 그룹이 1개뿐이면 드롭다운 없이 바로 표시
|
||||
if (groupedColumns.length <= 1) {
|
||||
return (
|
||||
<div key={group.tableName} className="space-y-1.5">
|
||||
{group.columns.map((column) => {
|
||||
const uniqueKey = `${column.tableName}.${column.columnName}`;
|
||||
const isSelected = selectedColumn === uniqueKey;
|
||||
return (
|
||||
<div
|
||||
key={uniqueKey}
|
||||
onClick={() => onColumnSelect(uniqueKey, column.columnLabel || column.columnName, column.tableName)}
|
||||
className={`cursor-pointer rounded-lg border px-4 py-2 transition-all ${
|
||||
isSelected ? "border-primary bg-primary/10 shadow-sm" : "hover:bg-muted/50"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<FolderTree
|
||||
className={`h-4 w-4 ${isSelected ? "text-primary" : "text-muted-foreground"}`}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-semibold">{column.columnLabel || column.columnName}</h4>
|
||||
<p className="text-muted-foreground text-xs">{column.tableLabel || column.tableName}</p>
|
||||
</div>
|
||||
<span className="text-muted-foreground text-xs font-medium">
|
||||
{column.valueCount !== undefined ? `${column.valueCount}개` : "..."}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={group.tableName} className="overflow-hidden rounded-lg border">
|
||||
{/* 드롭다운 헤더 */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setExpandedGroups((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(group.tableName)) {
|
||||
next.delete(group.tableName);
|
||||
} else {
|
||||
next.add(group.tableName);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
className={`flex w-full items-center gap-2 px-3 py-2 text-left transition-colors ${
|
||||
hasSelectedInGroup ? "bg-primary/5" : "hover:bg-muted/50"
|
||||
}`}
|
||||
>
|
||||
<ChevronRight
|
||||
className={`h-3.5 w-3.5 shrink-0 transition-transform duration-200 ${
|
||||
isExpanded ? "rotate-90" : ""
|
||||
} ${hasSelectedInGroup ? "text-primary" : "text-muted-foreground"}`}
|
||||
/>
|
||||
<span className={`flex-1 text-xs font-semibold ${hasSelectedInGroup ? "text-primary" : ""}`}>
|
||||
{group.tableLabel}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-[10px]">
|
||||
{group.columns.length}개 컬럼 / {totalValues}개 값
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* 펼쳐진 컬럼 목록 */}
|
||||
{isExpanded && (
|
||||
<div className="space-y-1 border-t px-2 py-2">
|
||||
{group.columns.map((column) => {
|
||||
const uniqueKey = `${column.tableName}.${column.columnName}`;
|
||||
const isSelected = selectedColumn === uniqueKey;
|
||||
return (
|
||||
<div
|
||||
key={uniqueKey}
|
||||
onClick={() => onColumnSelect(uniqueKey, column.columnLabel || column.columnName, column.tableName)}
|
||||
className={`cursor-pointer rounded-md px-3 py-1.5 transition-all ${
|
||||
isSelected ? "bg-primary/10 font-semibold text-primary" : "hover:bg-muted/50"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<FolderTree
|
||||
className={`h-3.5 w-3.5 ${isSelected ? "text-primary" : "text-muted-foreground"}`}
|
||||
/>
|
||||
<span className="flex-1 text-xs">{column.columnLabel || column.columnName}</span>
|
||||
<span className="text-muted-foreground text-[10px]">
|
||||
{column.valueCount !== undefined ? `${column.valueCount}개` : "..."}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user