feat: 날짜 기간 검색 기능 구현
- ModernDatePicker: 로컬 상태 관리로 즉시 검색 방지
- tempValue 상태 추가하여 확인 버튼 클릭 시에만 검색 실행
- 빠른 선택 버튼 추가 (오늘, 이번주, 이번달, 최근 7일, 최근 30일)
- TableSearchWidget: ModernDatePicker 통합
- 기본 HTML input[type=date]를 ModernDatePicker로 교체
- 날짜 범위 객체 {from, to}를 파이프 구분 문자열로 변환
- 백엔드 재시작 없이 작동하도록 임시 포맷팅 적용
- tableManagementService: 날짜 범위 검색 로직 개선
- getColumnWebTypeInfo: web_type이 null이면 input_type 폴백
- buildDateRangeCondition: VARCHAR 타입 날짜 컬럼 지원
- 날짜 컬럼을 ::date로 캐스팅하여 타입 호환성 확보
- 파이프 구분 문자열 파싱 지원 (YYYY-MM-DD|YYYY-MM-DD)
- 디버깅 로깅 추가
- 컬럼 타입 정보 조회 결과 로깅
- 날짜 범위 검색 조건 생성 과정 추적
This commit is contained in:
@@ -11,6 +11,7 @@ import { FilterPanel } from "@/components/screen/table-options/FilterPanel";
|
||||
import { GroupingPanel } from "@/components/screen/table-options/GroupingPanel";
|
||||
import { TableFilter } from "@/types/table-options";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { ModernDatePicker } from "@/components/screen/filters/ModernDatePicker";
|
||||
|
||||
interface PresetFilter {
|
||||
id: string;
|
||||
@@ -62,7 +63,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
|
||||
// 활성화된 필터 목록
|
||||
const [activeFilters, setActiveFilters] = useState<TableFilter[]>([]);
|
||||
const [filterValues, setFilterValues] = useState<Record<string, string>>({});
|
||||
const [filterValues, setFilterValues] = useState<Record<string, any>>({});
|
||||
// select 타입 필터의 옵션들
|
||||
const [selectOptions, setSelectOptions] = useState<Record<string, Array<{ label: string; value: string }>>>({});
|
||||
// 선택된 값의 라벨 저장 (데이터 없을 때도 라벨 유지)
|
||||
@@ -230,7 +231,7 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
const hasMultipleTables = tableList.length > 1;
|
||||
|
||||
// 필터 값 변경 핸들러
|
||||
const handleFilterChange = (columnName: string, value: string) => {
|
||||
const handleFilterChange = (columnName: string, value: any) => {
|
||||
const newValues = {
|
||||
...filterValues,
|
||||
[columnName]: value,
|
||||
@@ -243,14 +244,51 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
};
|
||||
|
||||
// 필터 적용 함수
|
||||
const applyFilters = (values: Record<string, string> = filterValues) => {
|
||||
const applyFilters = (values: Record<string, any> = filterValues) => {
|
||||
// 빈 값이 아닌 필터만 적용
|
||||
const filtersWithValues = activeFilters
|
||||
.map((filter) => ({
|
||||
...filter,
|
||||
value: values[filter.columnName] || "",
|
||||
}))
|
||||
.filter((f) => f.value !== "");
|
||||
.map((filter) => {
|
||||
let filterValue = values[filter.columnName];
|
||||
|
||||
// 날짜 범위 객체를 처리
|
||||
if (filter.filterType === "date" && filterValue && typeof filterValue === "object" && (filterValue.from || filterValue.to)) {
|
||||
// 날짜 범위 객체를 문자열 형식으로 변환 (백엔드 재시작 불필요)
|
||||
const formatDate = (date: Date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
// "YYYY-MM-DD|YYYY-MM-DD" 형식으로 변환
|
||||
const fromStr = filterValue.from ? formatDate(filterValue.from) : "";
|
||||
const toStr = filterValue.to ? formatDate(filterValue.to) : "";
|
||||
|
||||
if (fromStr && toStr) {
|
||||
// 둘 다 있으면 파이프로 연결
|
||||
filterValue = `${fromStr}|${toStr}`;
|
||||
} else if (fromStr) {
|
||||
// 시작일만 있으면
|
||||
filterValue = `${fromStr}|`;
|
||||
} else if (toStr) {
|
||||
// 종료일만 있으면
|
||||
filterValue = `|${toStr}`;
|
||||
} else {
|
||||
filterValue = "";
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...filter,
|
||||
value: filterValue || "",
|
||||
};
|
||||
})
|
||||
.filter((f) => {
|
||||
// 빈 값 체크
|
||||
if (!f.value) return false;
|
||||
if (typeof f.value === "string" && f.value === "") return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
currentTable?.onFilterChange(filtersWithValues);
|
||||
};
|
||||
@@ -271,14 +309,21 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||
switch (filter.filterType) {
|
||||
case "date":
|
||||
return (
|
||||
<Input
|
||||
type="date"
|
||||
value={value}
|
||||
onChange={(e) => handleFilterChange(filter.columnName, e.target.value)}
|
||||
className="h-9 text-xs focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none sm:text-sm"
|
||||
style={{ width: `${width}px`, height: "36px", minHeight: "36px", outline: "none", boxShadow: "none" }}
|
||||
placeholder={column?.columnLabel}
|
||||
/>
|
||||
<div style={{ width: `${width}px` }}>
|
||||
<ModernDatePicker
|
||||
label={column?.columnLabel || filter.columnName}
|
||||
value={value ? (typeof value === 'string' ? { from: new Date(value), to: new Date(value) } : value) : {}}
|
||||
onChange={(dateRange) => {
|
||||
if (dateRange.from && dateRange.to) {
|
||||
// 기간이 선택되면 from과 to를 모두 저장
|
||||
handleFilterChange(filter.columnName, dateRange);
|
||||
} else {
|
||||
handleFilterChange(filter.columnName, "");
|
||||
}
|
||||
}}
|
||||
includeTime={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
case "number":
|
||||
|
||||
Reference in New Issue
Block a user