툴바 선택

This commit is contained in:
leeheejin 2025-12-09 18:26:36 +09:00
parent 36a7529da2
commit 93ec294be3
3 changed files with 263 additions and 162 deletions

View File

@ -4675,19 +4675,21 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
</Popover>
{/* 새로고침 버튼 */}
<Button
variant="ghost"
size="sm"
onClick={handleRefresh}
disabled={loading}
className="h-8 w-8 p-0 sm:h-9 sm:w-auto sm:px-3"
>
<RefreshCw className={cn("h-3 w-3", loading && "animate-spin")} />
</Button>
{(tableConfig.toolbar?.showRefresh ?? true) && (
<Button
variant="ghost"
size="sm"
onClick={handleRefresh}
disabled={loading}
className="h-8 w-8 p-0 sm:h-9 sm:w-auto sm:px-3"
>
<RefreshCw className={cn("h-3 w-3", loading && "animate-spin")} />
</Button>
)}
</div>
</div>
);
}, [tableConfig.pagination, isDesignMode, currentPage, totalPages, totalItems, loading, selectedRows.size, exportToExcel, exportToPdf]);
}, [tableConfig.pagination, tableConfig.toolbar?.showRefresh, isDesignMode, currentPage, totalPages, totalItems, loading, selectedRows.size, exportToExcel, exportToPdf]);
// ========================================
// 렌더링
@ -4790,57 +4792,67 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
{/* 🆕 DevExpress 스타일 기능 툴바 */}
<div className="border-border bg-muted/20 flex flex-wrap items-center gap-1 border-b px-2 py-1.5 sm:gap-2 sm:px-4 sm:py-2">
{/* 편집 모드 토글 */}
<div className="flex items-center gap-1 border-r border-border pr-2">
<Button
variant={editMode === "batch" ? "default" : "ghost"}
size="sm"
onClick={() => setEditMode(editMode === "batch" ? "immediate" : "batch")}
className="h-7 text-xs"
title="배치 편집 모드 (Ctrl+B)"
>
<Edit className="mr-1 h-3 w-3" />
{editMode === "batch" ? "배치 모드" : "즉시 저장"}
</Button>
</div>
{(tableConfig.toolbar?.showEditMode ?? true) && (
<div className="flex items-center gap-1 border-r border-border pr-2">
<Button
variant={editMode === "batch" ? "default" : "ghost"}
size="sm"
onClick={() => setEditMode(editMode === "batch" ? "immediate" : "batch")}
className="h-7 text-xs"
title="배치 편집 모드 (Ctrl+B)"
>
<Edit className="mr-1 h-3 w-3" />
{editMode === "batch" ? "배치 모드" : "즉시 저장"}
</Button>
</div>
)}
{/* 내보내기 버튼들 */}
<div className="flex items-center gap-1 border-r border-border pr-2">
<Button
variant="ghost"
size="sm"
onClick={() => exportToExcel(true)}
className="h-7 text-xs"
title="Excel 내보내기"
>
<FileSpreadsheet className="mr-1 h-3 w-3 text-green-600" />
Excel
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => exportToPdf(true)}
className="h-7 text-xs"
title="PDF 내보내기"
>
<FileText className="mr-1 h-3 w-3 text-red-600" />
PDF
</Button>
</div>
{((tableConfig.toolbar?.showExcel ?? true) || (tableConfig.toolbar?.showPdf ?? true)) && (
<div className="flex items-center gap-1 border-r border-border pr-2">
{(tableConfig.toolbar?.showExcel ?? true) && (
<Button
variant="ghost"
size="sm"
onClick={() => exportToExcel(true)}
className="h-7 text-xs"
title="Excel 내보내기"
>
<FileSpreadsheet className="mr-1 h-3 w-3 text-green-600" />
Excel
</Button>
)}
{(tableConfig.toolbar?.showPdf ?? true) && (
<Button
variant="ghost"
size="sm"
onClick={() => exportToPdf(true)}
className="h-7 text-xs"
title="PDF 내보내기"
>
<FileText className="mr-1 h-3 w-3 text-red-600" />
PDF
</Button>
)}
</div>
)}
{/* 복사 버튼 */}
<div className="flex items-center gap-1 border-r border-border pr-2">
<Button
variant="ghost"
size="sm"
onClick={handleCopy}
disabled={selectedRows.size === 0 && !focusedCell}
className="h-7 text-xs"
title="복사 (Ctrl+C)"
>
<Copy className="mr-1 h-3 w-3" />
</Button>
</div>
{(tableConfig.toolbar?.showCopy ?? true) && (
<div className="flex items-center gap-1 border-r border-border pr-2">
<Button
variant="ghost"
size="sm"
onClick={handleCopy}
disabled={selectedRows.size === 0 && !focusedCell}
className="h-7 text-xs"
title="복사 (Ctrl+C)"
>
<Copy className="mr-1 h-3 w-3" />
</Button>
</div>
)}
{/* 선택 정보 */}
{selectedRows.size > 0 && (
@ -4861,124 +4873,130 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
)}
{/* 🆕 통합 검색 패널 */}
<div className="flex items-center gap-1 border-r border-border pr-2">
{isSearchPanelOpen ? (
<div className="flex items-center gap-1">
<input
type="text"
value={globalSearchTerm}
onChange={(e) => setGlobalSearchTerm(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
executeGlobalSearch(globalSearchTerm);
} else if (e.key === "Escape") {
clearGlobalSearch();
} else if (e.key === "F3" || (e.key === "g" && (e.ctrlKey || e.metaKey))) {
e.preventDefault();
if (e.shiftKey) {
goToPrevSearchResult();
} else {
goToNextSearchResult();
{(tableConfig.toolbar?.showSearch ?? true) && (
<div className="flex items-center gap-1 border-r border-border pr-2">
{isSearchPanelOpen ? (
<div className="flex items-center gap-1">
<input
type="text"
value={globalSearchTerm}
onChange={(e) => setGlobalSearchTerm(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
executeGlobalSearch(globalSearchTerm);
} else if (e.key === "Escape") {
clearGlobalSearch();
} else if (e.key === "F3" || (e.key === "g" && (e.ctrlKey || e.metaKey))) {
e.preventDefault();
if (e.shiftKey) {
goToPrevSearchResult();
} else {
goToNextSearchResult();
}
}
}
}}
placeholder="검색어 입력... (Enter)"
className="border-input bg-background h-7 w-32 rounded border px-2 text-xs focus:outline-none focus:ring-1 focus:ring-primary sm:w-48"
autoFocus
/>
{searchHighlights.size > 0 && (
<span className="text-muted-foreground text-xs">
{searchHighlights.size}
}}
placeholder="검색어 입력... (Enter)"
className="border-input bg-background h-7 w-32 rounded border px-2 text-xs focus:outline-none focus:ring-1 focus:ring-primary sm:w-48"
autoFocus
/>
{searchHighlights.size > 0 && (
<span className="text-muted-foreground text-xs">
{searchHighlights.size}
</span>
)}
<Button
variant="ghost"
size="sm"
onClick={goToPrevSearchResult}
disabled={searchHighlights.size === 0}
className="h-6 w-6 p-0"
title="이전 (Shift+F3)"
>
<ChevronLeft className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={goToNextSearchResult}
disabled={searchHighlights.size === 0}
className="h-6 w-6 p-0"
title="다음 (F3)"
>
<ChevronRight className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={clearGlobalSearch}
className="h-6 w-6 p-0"
title="닫기 (Esc)"
>
<X className="h-3 w-3" />
</Button>
</div>
) : (
<Button
variant="ghost"
size="sm"
onClick={() => setIsSearchPanelOpen(true)}
className="h-7 text-xs"
title="통합 검색 (Ctrl+F)"
>
<Filter className="mr-1 h-3 w-3" />
</Button>
)}
</div>
)}
{/* 🆕 Filter Builder (고급 필터) 버튼 */}
{(tableConfig.toolbar?.showFilter ?? true) && (
<div className="flex items-center gap-1 border-r border-border pr-2">
<Button
variant={activeFilterCount > 0 ? "default" : "ghost"}
size="sm"
onClick={() => setIsFilterBuilderOpen(true)}
className="h-7 text-xs"
title="고급 필터"
>
<Layers className="mr-1 h-3 w-3" />
{activeFilterCount > 0 && (
<span className="ml-1 rounded-full bg-white/20 px-1.5 text-[10px]">
{activeFilterCount}
</span>
)}
</Button>
{activeFilterCount > 0 && (
<Button
variant="ghost"
size="sm"
onClick={goToPrevSearchResult}
disabled={searchHighlights.size === 0}
onClick={clearFilterBuilder}
className="h-6 w-6 p-0"
title="이전 (Shift+F3)"
>
<ChevronLeft className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={goToNextSearchResult}
disabled={searchHighlights.size === 0}
className="h-6 w-6 p-0"
title="다음 (F3)"
>
<ChevronRight className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={clearGlobalSearch}
className="h-6 w-6 p-0"
title="닫기 (Esc)"
title="필터 초기화"
>
<X className="h-3 w-3" />
</Button>
</div>
) : (
<Button
variant="ghost"
size="sm"
onClick={() => setIsSearchPanelOpen(true)}
className="h-7 text-xs"
title="통합 검색 (Ctrl+F)"
>
<Filter className="mr-1 h-3 w-3" />
</Button>
)}
</div>
{/* 🆕 Filter Builder (고급 필터) 버튼 */}
<div className="flex items-center gap-1 border-r border-border pr-2">
<Button
variant={activeFilterCount > 0 ? "default" : "ghost"}
size="sm"
onClick={() => setIsFilterBuilderOpen(true)}
className="h-7 text-xs"
title="고급 필터"
>
<Layers className="mr-1 h-3 w-3" />
{activeFilterCount > 0 && (
<span className="ml-1 rounded-full bg-white/20 px-1.5 text-[10px]">
{activeFilterCount}
</span>
)}
</Button>
{activeFilterCount > 0 && (
<Button
variant="ghost"
size="sm"
onClick={clearFilterBuilder}
className="h-6 w-6 p-0"
title="필터 초기화"
>
<X className="h-3 w-3" />
</Button>
)}
</div>
</div>
)}
{/* 새로고침 */}
<div className="ml-auto flex items-center gap-1">
<Button
variant="ghost"
size="sm"
onClick={handleRefresh}
disabled={loading}
className="h-7 text-xs"
title="새로고침"
>
<RefreshCw className={cn("mr-1 h-3 w-3", loading && "animate-spin")} />
</Button>
</div>
{(tableConfig.toolbar?.showRefresh ?? true) && (
<div className="ml-auto flex items-center gap-1">
<Button
variant="ghost"
size="sm"
onClick={handleRefresh}
disabled={loading}
className="h-7 text-xs"
title="새로고침"
>
<RefreshCw className={cn("mr-1 h-3 w-3", loading && "animate-spin")} />
</Button>
</div>
)}
</div>
{/* 🆕 배치 편집 툴바 */}

View File

@ -765,6 +765,73 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
</div>
</div>
{/* 툴바 버튼 설정 */}
<div className="space-y-3">
<div>
<h3 className="text-sm font-semibold"> </h3>
<p className="text-muted-foreground text-[10px]"> </p>
</div>
<hr className="border-border" />
<div className="grid grid-cols-2 gap-2">
<div className="flex items-center space-x-2">
<Checkbox
id="showEditMode"
checked={config.toolbar?.showEditMode ?? true}
onCheckedChange={(checked) => handleNestedChange("toolbar", "showEditMode", checked)}
/>
<Label htmlFor="showEditMode" className="text-xs"> </Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="showExcel"
checked={config.toolbar?.showExcel ?? true}
onCheckedChange={(checked) => handleNestedChange("toolbar", "showExcel", checked)}
/>
<Label htmlFor="showExcel" className="text-xs">Excel</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="showPdf"
checked={config.toolbar?.showPdf ?? true}
onCheckedChange={(checked) => handleNestedChange("toolbar", "showPdf", checked)}
/>
<Label htmlFor="showPdf" className="text-xs">PDF</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="showCopy"
checked={config.toolbar?.showCopy ?? true}
onCheckedChange={(checked) => handleNestedChange("toolbar", "showCopy", checked)}
/>
<Label htmlFor="showCopy" className="text-xs"></Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="showSearch"
checked={config.toolbar?.showSearch ?? true}
onCheckedChange={(checked) => handleNestedChange("toolbar", "showSearch", checked)}
/>
<Label htmlFor="showSearch" className="text-xs"></Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="showFilter"
checked={config.toolbar?.showFilter ?? true}
onCheckedChange={(checked) => handleNestedChange("toolbar", "showFilter", checked)}
/>
<Label htmlFor="showFilter" className="text-xs"></Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="showRefresh"
checked={config.toolbar?.showRefresh ?? true}
onCheckedChange={(checked) => handleNestedChange("toolbar", "showRefresh", checked)}
/>
<Label htmlFor="showRefresh" className="text-xs"></Label>
</div>
</div>
</div>
{/* 체크박스 설정 */}
<div className="space-y-3">
<div>

View File

@ -164,6 +164,19 @@ export interface PaginationConfig {
pageSizeOptions: number[];
}
/**
*
*/
export interface ToolbarConfig {
showEditMode?: boolean; // 즉시 저장/배치 모드 버튼
showExcel?: boolean; // Excel 내보내기 버튼
showPdf?: boolean; // PDF 내보내기 버튼
showCopy?: boolean; // 복사 버튼
showSearch?: boolean; // 검색 버튼
showFilter?: boolean; // 필터 버튼
showRefresh?: boolean; // 새로고침 버튼
}
/**
*
*/
@ -259,6 +272,9 @@ export interface TableListConfig extends ComponentConfig {
autoLoad: boolean;
refreshInterval?: number; // 초 단위
// 🆕 툴바 버튼 표시 설정
toolbar?: ToolbarConfig;
// 🆕 컬럼 값 기반 데이터 필터링
dataFilter?: DataFilterConfig;