feat(WEB): UniversalListPage 날짜 범위 필터 자동 적용 기능 추가

UniversalListPage:
- dateRangeSelector.dateField 설정 시 클라이언트 사이드 날짜 필터 자동 적용
- 종료일 23:59:59까지 포함하도록 처리

AttendanceManagement:
- 사유 등록 버튼을 extraFilters에서 headerActions로 이동

IntegratedListTemplateV2:
- 날짜 범위 관련 타입 및 처리 개선

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-28 09:57:12 +09:00
parent 3157fb9401
commit 805063c686
6 changed files with 40 additions and 15 deletions

View File

@@ -469,6 +469,14 @@ export function AttendanceManagement() {
onClick: handleAddAttendance,
},
// 헤더 액션 (근태 등록 버튼 옆에 표시)
headerActions: () => (
<Button variant="outline" onClick={handleAddReason}>
<FileText className="w-4 h-4 mr-2" />
</Button>
),
searchPlaceholder: '이름, 부서 검색...',
// 엑셀 다운로드 설정 (클라이언트 사이드 필터링이므로 filteredData 사용)
@@ -478,15 +486,6 @@ export function AttendanceManagement() {
sheetName: '근태',
},
extraFilters: (
<div className="flex items-center gap-2 flex-wrap">
<Button variant="outline" onClick={handleAddReason}>
<FileText className="w-4 h-4 mr-2" />
</Button>
</div>
),
itemsPerPage: itemsPerPage,
clientSideFiltering: true,

View File

@@ -153,6 +153,8 @@ export function EmployeeManagement() {
);
}
// 날짜 필터는 UniversalListPage에서 dateField 설정을 통해 자동 처리됨
// 정렬
filtered = [...filtered].sort((a, b) => {
switch (sortOption) {
@@ -450,6 +452,7 @@ export function EmployeeManagement() {
endDate,
onStartDateChange: setStartDate,
onEndDateChange: setEndDate,
dateField: 'hireDate', // 입사일 기준 자동 필터링
},
createButton: {

View File

@@ -182,10 +182,11 @@ export function DateRangeSelector({
}
// presetsPosition이 'inline' (기본값)
// PC(1280px+): 달력 | 프리셋버튼 | 검색창 (한 줄)
// PC(1280px+): 달력 | 프리셋버튼 | 검색창 (한 줄, 넘치면 줄바꿈)
// 태블릿: 달력 / 프리셋버튼 / 검색창 (세 줄)
// Note: w-full 제거 - 부모 컨테이너에서 다른 요소들과 자연스럽게 한 줄에 배치되도록 함
return (
<div className="flex flex-col xl:flex-row xl:items-center gap-2 w-full">
<div className="flex flex-col xl:flex-row xl:flex-wrap xl:items-center gap-2">
{/* 날짜 범위 선택 */}
{!hideDateInputs && (
<div className="flex items-center gap-1 shrink-0">

View File

@@ -544,7 +544,7 @@ export function IntegratedListTemplateV2<T = any>({
{/* 레이아웃: [달력] [프리셋버튼] [검색창] -------------- [추가버튼들] [등록버튼] (오른쪽 끝) */}
{(dateRangeSelector?.enabled || createButton || headerActions || (hideSearch && onSearchChange)) && (
isLoading ? renderHeaderActionSkeleton() : (
<div className="flex flex-col xl:flex-row xl:items-center gap-2 w-full">
<div className="flex flex-col xl:flex-row xl:flex-wrap xl:items-center xl:justify-between gap-2 w-full">
{/* 날짜 범위 선택기 + 검색창 (왼쪽) */}
{dateRangeSelector?.enabled ? (
<DateRangeSelector
@@ -589,9 +589,9 @@ export function IntegratedListTemplateV2<T = any>({
</div>
)
)}
{/* 버튼 영역 (오른쪽 끝으로 통합) */}
{/* 버튼 영역 (오른쪽 배치, 공간 부족시 자연스럽게 줄바꿈) */}
{(headerActions || createButton) && (
<div className="flex items-center gap-2 ml-auto shrink-0">
<div className="flex items-center gap-2 shrink-0">
{/* 헤더 액션 (엑셀 다운로드 등 추가 버튼들) */}
{headerActions}
{/* 등록 버튼 */}

View File

@@ -122,6 +122,22 @@ export function UniversalListPage<T>({
);
}
// 날짜 범위 필터 (dateRangeSelector.dateField 설정 시 자동 적용)
const { dateRangeSelector } = config;
if (dateRangeSelector?.enabled && dateRangeSelector.dateField && dateRangeSelector.startDate && dateRangeSelector.endDate) {
const dateField = dateRangeSelector.dateField;
const start = new Date(dateRangeSelector.startDate);
const end = new Date(dateRangeSelector.endDate);
end.setHours(23, 59, 59, 999); // 종료일 끝까지 포함
filtered = filtered.filter((item) => {
const itemDate = (item as Record<string, unknown>)[dateField];
if (!itemDate) return false;
const date = new Date(String(itemDate));
return date >= start && date <= end;
});
}
// 커스텀 정렬 함수
if (config.customSortFn) {
filtered = config.customSortFn(filtered, filters);
@@ -152,7 +168,7 @@ export function UniversalListPage<T>({
}
return filtered;
}, [rawData, activeTab, searchValue, filters, sortBy, sortOrder, config.clientSideFiltering, config.tabFilter, config.searchFilter, config.customFilterFn, config.customSortFn]);
}, [rawData, activeTab, searchValue, filters, sortBy, sortOrder, config.clientSideFiltering, config.tabFilter, config.searchFilter, config.customFilterFn, config.customSortFn, config.dateRangeSelector]);
// 클라이언트 사이드 페이지네이션
const paginatedData = useMemo(() => {

View File

@@ -277,6 +277,12 @@ export interface UniversalListConfig<T> {
onEndDateChange?: (date: string) => void;
/** 추가 액션 (검색창 등) - presetsPosition이 'below'일 때 달력 옆에 배치됨 */
extraActions?: ReactNode;
/**
* 날짜 필터링에 사용할 필드명 (clientSideFiltering: true 시 자동 필터링)
* - 예: 'hireDate', 'createdAt', 'orderDate' 등
* - 설정 시 startDate ~ endDate 범위로 해당 필드 자동 필터링
*/
dateField?: string;
};
/**
* 등록 버튼 (오른쪽 끝 배치)