[feat] negosium: 협상 리스트 마감기준 정렬 컨트롤
- SortControl(기본순/마감임박순/여유순) 추가, StatusTabs 우측에 배치 - store setDeadline 배선 — 선택값을 서버 정렬(deadlineToOrder)로 전달
This commit is contained in:
parent
82cca894fc
commit
35d38e0818
37
frontend/src/features/list/components/SortControl.tsx
Normal file
37
frontend/src/features/list/components/SortControl.tsx
Normal file
@ -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 (
|
||||
<div className="flex items-center gap-0.5 rounded-xl border border-border bg-neutral-10 p-0.5">
|
||||
{SORT_OPTIONS.map((o) => {
|
||||
const active = selected === o.value
|
||||
return (
|
||||
<button
|
||||
key={o.label}
|
||||
type="button"
|
||||
onClick={() => onSelect(o.value)}
|
||||
className={cn(
|
||||
'whitespace-nowrap rounded-[10px] px-2.5 py-[6px] text-xs font-bold transition-all active:scale-[0.98]',
|
||||
active ? 'bg-white text-brand-700 shadow-sm' : 'text-neutral-60 hover:text-neutral-80',
|
||||
)}
|
||||
>
|
||||
{o.label}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -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,19 +36,22 @@ const REJECT_BLOCKED: Record<string, string> = {
|
||||
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(
|
||||
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,
|
||||
})),
|
||||
)
|
||||
@ -153,6 +157,7 @@ export function ListWorkspace() {
|
||||
<div className="flex flex-col gap-3 border-b border-border p-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<StatusTabs selected={selectedStatus} onSelect={setStatus} />
|
||||
<div className="flex w-full items-center gap-2 lg:w-auto">
|
||||
<SortControl selected={selectedDeadline} onSelect={setDeadline} />
|
||||
<div className="relative flex-1 lg:flex-none">
|
||||
<Search className="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-neutral-50" />
|
||||
<input
|
||||
|
||||
@ -13,6 +13,7 @@ interface ListState {
|
||||
toggleStatus: (item: string) => 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<ListState>((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 }),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user