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:
@@ -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,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}
|
||||
{/* 등록 버튼 */}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
/**
|
||||
* 등록 버튼 (오른쪽 끝 배치)
|
||||
|
||||
Reference in New Issue
Block a user