툴바 선택
This commit is contained in:
parent
36a7529da2
commit
93ec294be3
|
|
@ -4675,19 +4675,21 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
{/* 새로고침 버튼 */}
|
{/* 새로고침 버튼 */}
|
||||||
<Button
|
{(tableConfig.toolbar?.showRefresh ?? true) && (
|
||||||
variant="ghost"
|
<Button
|
||||||
size="sm"
|
variant="ghost"
|
||||||
onClick={handleRefresh}
|
size="sm"
|
||||||
disabled={loading}
|
onClick={handleRefresh}
|
||||||
className="h-8 w-8 p-0 sm:h-9 sm:w-auto sm:px-3"
|
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>
|
<RefreshCw className={cn("h-3 w-3", loading && "animate-spin")} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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 스타일 기능 툴바 */}
|
{/* 🆕 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="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">
|
{(tableConfig.toolbar?.showEditMode ?? true) && (
|
||||||
<Button
|
<div className="flex items-center gap-1 border-r border-border pr-2">
|
||||||
variant={editMode === "batch" ? "default" : "ghost"}
|
<Button
|
||||||
size="sm"
|
variant={editMode === "batch" ? "default" : "ghost"}
|
||||||
onClick={() => setEditMode(editMode === "batch" ? "immediate" : "batch")}
|
size="sm"
|
||||||
className="h-7 text-xs"
|
onClick={() => setEditMode(editMode === "batch" ? "immediate" : "batch")}
|
||||||
title="배치 편집 모드 (Ctrl+B)"
|
className="h-7 text-xs"
|
||||||
>
|
title="배치 편집 모드 (Ctrl+B)"
|
||||||
<Edit className="mr-1 h-3 w-3" />
|
>
|
||||||
{editMode === "batch" ? "배치 모드" : "즉시 저장"}
|
<Edit className="mr-1 h-3 w-3" />
|
||||||
</Button>
|
{editMode === "batch" ? "배치 모드" : "즉시 저장"}
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 내보내기 버튼들 */}
|
{/* 내보내기 버튼들 */}
|
||||||
<div className="flex items-center gap-1 border-r border-border pr-2">
|
{((tableConfig.toolbar?.showExcel ?? true) || (tableConfig.toolbar?.showPdf ?? true)) && (
|
||||||
<Button
|
<div className="flex items-center gap-1 border-r border-border pr-2">
|
||||||
variant="ghost"
|
{(tableConfig.toolbar?.showExcel ?? true) && (
|
||||||
size="sm"
|
<Button
|
||||||
onClick={() => exportToExcel(true)}
|
variant="ghost"
|
||||||
className="h-7 text-xs"
|
size="sm"
|
||||||
title="Excel 내보내기"
|
onClick={() => exportToExcel(true)}
|
||||||
>
|
className="h-7 text-xs"
|
||||||
<FileSpreadsheet className="mr-1 h-3 w-3 text-green-600" />
|
title="Excel 내보내기"
|
||||||
Excel
|
>
|
||||||
</Button>
|
<FileSpreadsheet className="mr-1 h-3 w-3 text-green-600" />
|
||||||
<Button
|
Excel
|
||||||
variant="ghost"
|
</Button>
|
||||||
size="sm"
|
)}
|
||||||
onClick={() => exportToPdf(true)}
|
{(tableConfig.toolbar?.showPdf ?? true) && (
|
||||||
className="h-7 text-xs"
|
<Button
|
||||||
title="PDF 내보내기"
|
variant="ghost"
|
||||||
>
|
size="sm"
|
||||||
<FileText className="mr-1 h-3 w-3 text-red-600" />
|
onClick={() => exportToPdf(true)}
|
||||||
PDF
|
className="h-7 text-xs"
|
||||||
</Button>
|
title="PDF 내보내기"
|
||||||
</div>
|
>
|
||||||
|
<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">
|
{(tableConfig.toolbar?.showCopy ?? true) && (
|
||||||
<Button
|
<div className="flex items-center gap-1 border-r border-border pr-2">
|
||||||
variant="ghost"
|
<Button
|
||||||
size="sm"
|
variant="ghost"
|
||||||
onClick={handleCopy}
|
size="sm"
|
||||||
disabled={selectedRows.size === 0 && !focusedCell}
|
onClick={handleCopy}
|
||||||
className="h-7 text-xs"
|
disabled={selectedRows.size === 0 && !focusedCell}
|
||||||
title="복사 (Ctrl+C)"
|
className="h-7 text-xs"
|
||||||
>
|
title="복사 (Ctrl+C)"
|
||||||
<Copy className="mr-1 h-3 w-3" />
|
>
|
||||||
복사
|
<Copy className="mr-1 h-3 w-3" />
|
||||||
</Button>
|
복사
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 선택 정보 */}
|
{/* 선택 정보 */}
|
||||||
{selectedRows.size > 0 && (
|
{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">
|
{(tableConfig.toolbar?.showSearch ?? true) && (
|
||||||
{isSearchPanelOpen ? (
|
<div className="flex items-center gap-1 border-r border-border pr-2">
|
||||||
<div className="flex items-center gap-1">
|
{isSearchPanelOpen ? (
|
||||||
<input
|
<div className="flex items-center gap-1">
|
||||||
type="text"
|
<input
|
||||||
value={globalSearchTerm}
|
type="text"
|
||||||
onChange={(e) => setGlobalSearchTerm(e.target.value)}
|
value={globalSearchTerm}
|
||||||
onKeyDown={(e) => {
|
onChange={(e) => setGlobalSearchTerm(e.target.value)}
|
||||||
if (e.key === "Enter") {
|
onKeyDown={(e) => {
|
||||||
executeGlobalSearch(globalSearchTerm);
|
if (e.key === "Enter") {
|
||||||
} else if (e.key === "Escape") {
|
executeGlobalSearch(globalSearchTerm);
|
||||||
clearGlobalSearch();
|
} else if (e.key === "Escape") {
|
||||||
} else if (e.key === "F3" || (e.key === "g" && (e.ctrlKey || e.metaKey))) {
|
clearGlobalSearch();
|
||||||
e.preventDefault();
|
} else if (e.key === "F3" || (e.key === "g" && (e.ctrlKey || e.metaKey))) {
|
||||||
if (e.shiftKey) {
|
e.preventDefault();
|
||||||
goToPrevSearchResult();
|
if (e.shiftKey) {
|
||||||
} else {
|
goToPrevSearchResult();
|
||||||
goToNextSearchResult();
|
} else {
|
||||||
|
goToNextSearchResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
}}
|
placeholder="검색어 입력... (Enter)"
|
||||||
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"
|
||||||
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
|
||||||
autoFocus
|
/>
|
||||||
/>
|
{searchHighlights.size > 0 && (
|
||||||
{searchHighlights.size > 0 && (
|
<span className="text-muted-foreground text-xs">
|
||||||
<span className="text-muted-foreground text-xs">
|
{searchHighlights.size}개
|
||||||
{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>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
</Button>
|
||||||
|
{activeFilterCount > 0 && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={goToPrevSearchResult}
|
onClick={clearFilterBuilder}
|
||||||
disabled={searchHighlights.size === 0}
|
|
||||||
className="h-6 w-6 p-0"
|
className="h-6 w-6 p-0"
|
||||||
title="이전 (Shift+F3)"
|
title="필터 초기화"
|
||||||
>
|
|
||||||
<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" />
|
<X className="h-3 w-3" />
|
||||||
</Button>
|
</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>
|
</div>
|
||||||
{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 className="ml-auto flex items-center gap-1">
|
{(tableConfig.toolbar?.showRefresh ?? true) && (
|
||||||
<Button
|
<div className="ml-auto flex items-center gap-1">
|
||||||
variant="ghost"
|
<Button
|
||||||
size="sm"
|
variant="ghost"
|
||||||
onClick={handleRefresh}
|
size="sm"
|
||||||
disabled={loading}
|
onClick={handleRefresh}
|
||||||
className="h-7 text-xs"
|
disabled={loading}
|
||||||
title="새로고침"
|
className="h-7 text-xs"
|
||||||
>
|
title="새로고침"
|
||||||
<RefreshCw className={cn("mr-1 h-3 w-3", loading && "animate-spin")} />
|
>
|
||||||
새로고침
|
<RefreshCw className={cn("mr-1 h-3 w-3", loading && "animate-spin")} />
|
||||||
</Button>
|
새로고침
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 🆕 배치 편집 툴바 */}
|
{/* 🆕 배치 편집 툴바 */}
|
||||||
|
|
|
||||||
|
|
@ -765,6 +765,73 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</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 className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,19 @@ export interface PaginationConfig {
|
||||||
pageSizeOptions: number[];
|
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;
|
autoLoad: boolean;
|
||||||
refreshInterval?: number; // 초 단위
|
refreshInterval?: number; // 초 단위
|
||||||
|
|
||||||
|
// 🆕 툴바 버튼 표시 설정
|
||||||
|
toolbar?: ToolbarConfig;
|
||||||
|
|
||||||
// 🆕 컬럼 값 기반 데이터 필터링
|
// 🆕 컬럼 값 기반 데이터 필터링
|
||||||
dataFilter?: DataFilterConfig;
|
dataFilter?: DataFilterConfig;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue