feat(WEB): DynamicItemForm 필드 타입 확장 및 컴포넌트 레지스트리 추가

- DynamicFieldRenderer에 신규 필드 타입 추가 (Currency, File, MultiSelect, Radio, Reference, Toggle, UnitValue, Computed)
- DynamicTableSection 및 TableCellRenderer 추가
- 필드 프리셋 및 설정 구조 분리
- 컴포넌트 레지스트리 개발 도구 페이지 추가
- UniversalListPage 개선
- 근태관리 코드 정리
- 즐겨찾기 기능 및 동적 필드 타입 백엔드 스펙 문서 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-12 11:17:57 +09:00
parent 4decb99856
commit 020d74f36c
39 changed files with 12368 additions and 116 deletions

View File

@@ -671,30 +671,12 @@ export function UniversalListPage<T>({
toast.success(`${selectedData.length}건 다운로드 완료`);
}, [config.excelDownload, effectiveSelectedItems, rawData, getItemId]);
// 엑셀 다운로드 버튼 렌더링
// 엑셀 전체 다운로드 버튼 (헤더 영역)
const renderExcelDownloadButton = useMemo(() => {
if (!config.excelDownload || config.excelDownload.enabled === false || !canExport) {
return null;
}
const { enableSelectedDownload = true } = config.excelDownload;
// 선택 항목이 있고 선택 다운로드가 활성화된 경우
if (enableSelectedDownload && effectiveSelectedItems.size > 0) {
return (
<Button
variant="outline"
size="sm"
onClick={handleSelectedExcelDownload}
className="gap-2"
>
<Download className="h-4 w-4" />
({effectiveSelectedItems.size})
</Button>
);
}
// 전체 다운로드
return (
<Button
variant="outline"
@@ -711,7 +693,30 @@ export function UniversalListPage<T>({
{isExcelDownloading ? '다운로드 중...' : '엑셀 다운로드'}
</Button>
);
}, [config.excelDownload, effectiveSelectedItems.size, isExcelDownloading, handleExcelDownload, handleSelectedExcelDownload]);
}, [config.excelDownload, canExport, isExcelDownloading, handleExcelDownload]);
// 엑셀 선택 다운로드 버튼 (selectionActions 영역 - "전체 N건 / N개 항목 선택됨" 뒤)
const renderExcelSelectedDownloadButton = useMemo(() => {
if (!config.excelDownload || config.excelDownload.enabled === false || !canExport) {
return null;
}
const { enableSelectedDownload = true } = config.excelDownload;
if (!enableSelectedDownload || effectiveSelectedItems.size === 0) {
return null;
}
return (
<Button
variant="outline"
size="sm"
onClick={handleSelectedExcelDownload}
className="gap-2"
>
<Download className="h-4 w-4" />
({effectiveSelectedItems.size})
</Button>
);
}, [config.excelDownload, canExport, effectiveSelectedItems.size, handleSelectedExcelDownload]);
// ===== 정렬 핸들러 =====
const handleSort = useCallback((key: string) => {
@@ -952,11 +957,16 @@ export function UniversalListPage<T>({
onToggleSelectAll={toggleSelectAll}
getItemId={effectiveGetItemId}
onBulkDelete={config.actions?.deleteItem ? handleBulkDeleteClick : undefined}
selectionActions={config.selectionActions?.({
selectedItems: effectiveSelectedItems,
onClearSelection: () => externalSelection ? undefined : setSelectedItems(new Set()),
onRefresh: fetchData,
})}
selectionActions={
<>
{renderExcelSelectedDownloadButton}
{config.selectionActions?.({
selectedItems: effectiveSelectedItems,
onClearSelection: () => externalSelection ? undefined : setSelectedItems(new Set()),
onRefresh: fetchData,
})}
</>
}
// 표시 옵션
showCheckbox={config.showCheckbox}
showRowNumber={config.showRowNumber}