From 35d38e0818f53e139b060ed289fdfa20f6d7dc88 Mon Sep 17 00:00:00 2001 From: Mina Choi Date: Wed, 29 Jul 2026 17:29:50 +0900 Subject: [PATCH] =?UTF-8?q?[feat]=20negosium:=20=ED=98=91=EC=83=81=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EB=A7=88=EA=B0=90=EA=B8=B0?= =?UTF-8?q?=EC=A4=80=20=EC=A0=95=EB=A0=AC=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SortControl(기본순/마감임박순/여유순) 추가, StatusTabs 우측에 배치 - store setDeadline 배선 — 선택값을 서버 정렬(deadlineToOrder)로 전달 --- .../features/list/components/SortControl.tsx | 37 +++++++++++++++++++ .../list/containers/ListWorkspace.tsx | 25 ++++++++----- .../src/features/list/stores/useListStore.ts | 2 + 3 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 frontend/src/features/list/components/SortControl.tsx 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 }),