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 }),