diff --git a/frontend/src/features/list/components/SortControl.tsx b/frontend/src/features/list/components/SortControl.tsx new file mode 100644 index 0000000..654f6b3 --- /dev/null +++ b/frontend/src/features/list/components/SortControl.tsx @@ -0,0 +1,37 @@ +import { cn } from '@/lib' + +// 마감 기준 정렬. value(null=기본 그룹정렬)는 store.selectedDeadline 로 그대로 전달되고, +// adapter.deadlineToOrder 가 '남은 시간 적은 순'=asc(임박), 그 외=desc(여유) 로 매핑한다. +const SORT_OPTIONS: { label: string; value: string | null }[] = [ + { label: '기본순', value: null }, + { label: '마감임박순', value: '남은 시간 적은 순' }, + { label: '여유순', value: '남은 시간 긴 순' }, +] + +interface SortControlProps { + selected: string | null + onSelect: (value: string | null) => void +} + +export function SortControl({ selected, onSelect }: SortControlProps) { + return ( +
+ {SORT_OPTIONS.map((o) => { + const active = selected === o.value + return ( + + ) + })} +
+ ) +} diff --git a/frontend/src/features/list/containers/ListWorkspace.tsx b/frontend/src/features/list/containers/ListWorkspace.tsx index 200e479..9299eb9 100644 --- a/frontend/src/features/list/containers/ListWorkspace.tsx +++ b/frontend/src/features/list/containers/ListWorkspace.tsx @@ -15,6 +15,7 @@ import { useList } from '@/features/list/hooks/useList' import { useListStore } from '@/features/list/stores/useListStore' import { KpiCards } from '@/features/list/components/KpiCards' import { StatusTabs } from '@/features/list/components/StatusTabs' +import { SortControl } from '@/features/list/components/SortControl' import { WorkspaceTable } from '@/features/list/components/WorkspaceTable' import { WorkspaceCards } from '@/features/list/components/WorkspaceCards' import { Pagination } from '@/features/list/components/Pagination' @@ -35,22 +36,25 @@ const REJECT_BLOCKED: Record = { export function ListWorkspace() { const navigate = useNavigate() const { data: user } = useMeQuery() - // 리스트는 서버 페이징. (검색은 백엔드 미지원 → 프론트 검색은 비워 둠) + // 리스트는 서버 페이징 + 서버 검색(keyword: 견적번호·상품명·상품코드 부분일치). const { items, isLoading, totalPages, currentPage, setCurrentPage } = useList() const participate = useParticipateMutation() const reject = useRejectMutation() const saveExtra = useSaveExtraInfoMutation() const requestRenego = useRequestRenegotiationMutation() - const { selectedStatus, selectedResult, setStatus, toggleResult, setKeyword } = useListStore( - useShallow((s) => ({ - selectedStatus: s.selectedStatus, - selectedResult: s.selectedResult, - setStatus: s.setStatus, - toggleResult: s.toggleResult, - setKeyword: s.setKeyword, - })), - ) + const { selectedStatus, selectedResult, selectedDeadline, setStatus, toggleResult, setDeadline, setKeyword } = + useListStore( + useShallow((s) => ({ + selectedStatus: s.selectedStatus, + selectedResult: s.selectedResult, + selectedDeadline: s.selectedDeadline, + setStatus: s.setStatus, + toggleResult: s.toggleResult, + setDeadline: s.setDeadline, + setKeyword: s.setKeyword, + })), + ) // 입력은 즉시 반영하되, 서버 요청은 300ms 디바운스(타이핑마다 호출 방지). 변경 시 store 가 페이지를 1로 되돌린다. const [search, setSearch] = useState('') @@ -153,6 +157,7 @@ export function ListWorkspace() {
+
void setStatus: (item: string | null) => void toggleDeadline: (item: string) => void + setDeadline: (item: string | null) => void toggleResult: (code: number) => void setKeyword: (keyword: string) => void setCurrentPage: (page: number) => void @@ -38,6 +39,7 @@ export const useListStore = create((set) => ({ setStatus: (item) => set({ selectedStatus: item, selectedResult: null, currentPage: 1 }), toggleDeadline: (item) => set((s) => ({ selectedDeadline: toggle(s.selectedDeadline, item), currentPage: 1 })), + setDeadline: (item) => set({ selectedDeadline: item, currentPage: 1 }), toggleResult: (code) => set((s) => ({ selectedResult: s.selectedResult === code ? null : code, selectedStatus: null, currentPage: 1 })), setKeyword: (keyword) => set({ keyword, currentPage: 1 }),