('all')
const {
isLoading,
isError,
isEmpty,
- pending,
- accepted,
- cancelled,
+ sections,
actionsDisabled,
actionModal,
onApproveClick,
onRejectClick,
onActionModalClose,
onActionModalSubmit,
- } = useManagerSubstituteRequestViewModel()
+ } = useManagerSubstituteRequestViewModel({ statusFilter })
return (
@@ -136,52 +166,36 @@ export function ManagerSubstituteRequestPage() {
) : isEmpty ? (
-
-
- 대타 요청이 없습니다.
-
+
+
+
+ {statusFilterLabel(statusFilter)}
+
+
+
+
) : (
<>
- {pending.length > 0 && (
-
- {pending.map(item => (
- onApproveClick(item.id)}
- onReject={() => onRejectClick(item.id)}
- disabled={actionsDisabled}
- />
- ))}
-
- )}
-
- {accepted.length > 0 && (
-
- {accepted.map(item => (
-
- ))}
-
- )}
-
- {cancelled.length > 0 && (
-
- {cancelled.map(item => (
-
- ))}
-
- )}
+ {sections.map((section, index) => (
+
+ ))}
>
)}
diff --git a/src/pages/user/substitute-request/components/SubstituteRequestListSections.tsx b/src/pages/user/substitute-request/components/SubstituteRequestListSections.tsx
index 06447b89..c14b6eed 100644
--- a/src/pages/user/substitute-request/components/SubstituteRequestListSections.tsx
+++ b/src/pages/user/substitute-request/components/SubstituteRequestListSections.tsx
@@ -1,13 +1,18 @@
-import DownIcon from '@/assets/icons/home/chevron-down.svg?react'
-
import { SubstituteRequestCard } from '@/pages/user/substitute-request/components/SubstituteRequestCard'
+import { SubstituteStatusFilterDropdown } from '@/pages/user/substitute-request/components/SubstituteStatusFilterDropdown'
import type { SubstituteDirectionTab } from '@/pages/user/substitute-request/components/SubstituteRequestTabs'
import type { SubstituteListSection } from '@/features/user/substitute/hooks/useUserSubstituteRequestsViewModel'
+import {
+ statusFilterLabel,
+ type SubstituteListStatusFilter,
+} from '@/features/user/substitute/lib/substituteListFilters'
import type { UserSubstituteListItem } from '@/features/user/substitute/types'
interface SubstituteRequestListSectionsProps {
sections: SubstituteListSection[]
directionTab: SubstituteDirectionTab
+ statusFilter?: SubstituteListStatusFilter
+ onStatusFilterChange?: (value: SubstituteListStatusFilter) => void
onItemClick: (item: UserSubstituteListItem) => void
onAccept?: (item: UserSubstituteListItem) => void
onReject?: (item: UserSubstituteListItem) => void
@@ -17,38 +22,51 @@ interface SubstituteRequestListSectionsProps {
export function SubstituteRequestListSections({
sections,
directionTab,
+ statusFilter,
+ onStatusFilterChange,
onItemClick,
onAccept,
onReject,
actionsDisabled,
}: SubstituteRequestListSectionsProps) {
+ const showStatusFilter = statusFilter != null && onStatusFilterChange != null
+
if (sections.length === 0) {
return (
-
-
- 대타 요청 내역이 없습니다.
-
+
+ {showStatusFilter ? (
+
+
+ {statusFilterLabel(statusFilter)}
+
+
+
+ ) : null}
+
)
}
return (
- {sections.map(section => (
+ {sections.map((section, index) => (
{section.title}
- {section.key === 'pending' ? (
-
+ {showStatusFilter && index === 0 ? (
+
) : null}
diff --git a/src/pages/user/substitute-request/components/SubstituteStatusFilterDropdown.tsx b/src/pages/user/substitute-request/components/SubstituteStatusFilterDropdown.tsx
new file mode 100644
index 00000000..54aa1886
--- /dev/null
+++ b/src/pages/user/substitute-request/components/SubstituteStatusFilterDropdown.tsx
@@ -0,0 +1,80 @@
+import { useEffect, useRef, useState } from 'react'
+
+import DownIcon from '@/assets/icons/home/chevron-down.svg?react'
+
+import {
+ SUBSTITUTE_STATUS_FILTER_OPTIONS,
+ statusFilterLabel,
+ type SubstituteListStatusFilter,
+} from '@/features/user/substitute/lib/substituteListFilters'
+
+interface SubstituteStatusFilterDropdownProps {
+ value: SubstituteListStatusFilter
+ onChange: (value: SubstituteListStatusFilter) => void
+}
+
+export function SubstituteStatusFilterDropdown({
+ value,
+ onChange,
+}: SubstituteStatusFilterDropdownProps) {
+ const [isOpen, setIsOpen] = useState(false)
+ const containerRef = useRef
(null)
+
+ useEffect(() => {
+ function handleOutsideClick(e: MouseEvent) {
+ if (
+ containerRef.current &&
+ !containerRef.current.contains(e.target as Node)
+ ) {
+ setIsOpen(false)
+ }
+ }
+ if (isOpen) {
+ document.addEventListener('mousedown', handleOutsideClick)
+ }
+ return () => document.removeEventListener('mousedown', handleOutsideClick)
+ }, [isOpen])
+
+ return (
+
+
+
+ {isOpen ? (
+
+ {SUBSTITUTE_STATUS_FILTER_OPTIONS.map(option => (
+ -
+
+
+ ))}
+
+ ) : null}
+
+ )
+}
diff --git a/src/pages/user/substitute-request/index.tsx b/src/pages/user/substitute-request/index.tsx
index b04e40e8..43d8d55b 100644
--- a/src/pages/user/substitute-request/index.tsx
+++ b/src/pages/user/substitute-request/index.tsx
@@ -16,6 +16,7 @@ import type {
UserSubstituteListItem,
} from '@/features/user/substitute/types'
import { useUserSubstituteRequestsViewModel } from '@/features/user/substitute/hooks/useUserSubstituteRequestsViewModel'
+import type { SubstituteListStatusFilter } from '@/features/user/substitute/lib/substituteListFilters'
import { useWorkspacesViewModel } from '@/features/user'
import { SubstituteCreateFab } from '@/pages/user/substitute-request/components/SubstituteCreateFab'
import { SubstituteRejectReasonModal } from '@/pages/user/substitute-request/components/SubstituteRejectReasonModal'
@@ -90,6 +91,10 @@ export function SubstituteRequestPage() {
locationState?.directionTab ?? locationState?.direction
) ?? 'sent'
)
+ const [sentStatusFilter, setSentStatusFilter] =
+ useState('all')
+ const [receivedStatusFilter, setReceivedStatusFilter] =
+ useState('all')
const [storePickerOpen, setStorePickerOpen] = useState(false)
const [createFlow, setCreateFlow] = useState(null)
const [rejectRequestId, setRejectRequestId] = useState(null)
@@ -97,12 +102,17 @@ export function SubstituteRequestPage() {
const { workspaces, isLoading: workspacesLoading } = useWorkspacesViewModel()
const apiDirection = directionTab === 'sent' ? 'SENT' : 'RECEIVED'
+ const statusFilter =
+ directionTab === 'sent' ? sentStatusFilter : receivedStatusFilter
+ const setStatusFilter =
+ directionTab === 'sent' ? setSentStatusFilter : setReceivedStatusFilter
+
const {
sections,
isLoading: listLoading,
isError: listError,
refetch,
- } = useUserSubstituteRequestsViewModel(apiDirection)
+ } = useUserSubstituteRequestsViewModel(apiDirection, { statusFilter })
const invalidateLists = async () => {
await queryClient.invalidateQueries({
@@ -257,6 +267,8 @@ export function SubstituteRequestPage() {
void
@@ -136,64 +131,34 @@ export function SubstituteRequestModalFlow({
출근 시간
-
+
:
-
+
@@ -202,70 +167,49 @@ export function SubstituteRequestModalFlow({
퇴근 시간
-
+
:
-
+
+
+ {!flow.hasSelectedSchedule ? (
+
+ 선택한 날짜에 등록된 근무 스케줄이 없습니다.
+
+ ) : null}