-
Notifications
You must be signed in to change notification settings - Fork 0
[fix] 대타 요청 비즈니스 로직 API 수정 #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
353c719
20431ab
6baaec9
c7fa4f4
3dd79da
e616be5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,13 +2,18 @@ import { useMemo } from 'react' | |
| import { useInfiniteQuery } from '@tanstack/react-query' | ||
| import { fetchSubstituteRequests } from '@/features/manager/api/substitute' | ||
| import { adaptSubstituteRequestDto } from '@/features/manager/home/types/substitute' | ||
| import { resolveManagerApiStatuses } from '@/features/manager/substitute/lib/managerSubstituteListFilters' | ||
| import type { SubstituteListStatusFilter } from '@/features/user/substitute/lib/substituteListFilters' | ||
|
Comment on lines
+5
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win 상태 필터 매핑을 feature 밖으로 올려주세요.
As per path instructions, “src/features/** … entities, shared 레이어만 import하는지”를 확인해야 합니다. 🤖 Prompt for AI AgentsSource: Path instructions |
||
| import { queryKeys } from '@/shared/lib/queryKeys' | ||
|
|
||
| export function useSubstituteRequestsViewModel( | ||
| workspaceId: number | null, | ||
| params?: { status?: string }, | ||
| params?: { statusFilter?: SubstituteListStatusFilter }, | ||
| pageSize = 10 | ||
| ) { | ||
| const statusFilter = params?.statusFilter ?? 'all' | ||
| const apiStatuses = resolveManagerApiStatuses(statusFilter) | ||
|
|
||
| const { | ||
| data, | ||
| fetchNextPage, | ||
|
|
@@ -19,14 +24,14 @@ export function useSubstituteRequestsViewModel( | |
| } = useInfiniteQuery({ | ||
| queryKey: queryKeys.substitute.list({ | ||
| workspaceId: workspaceId ?? undefined, | ||
| status: params?.status, | ||
| statusFilter, | ||
| pageSize, | ||
| }), | ||
| queryFn: ({ pageParam }) => | ||
| fetchSubstituteRequests({ | ||
| pageSize, | ||
| workspaceId: workspaceId ?? undefined, | ||
| status: params?.status, | ||
| status: apiStatuses.length > 0 ? apiStatuses : undefined, | ||
| cursor: pageParam as string | undefined, | ||
| }), | ||
| initialPageParam: undefined as string | undefined, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,10 +7,11 @@ import { | |
| approveSubstituteRequest, | ||
| rejectSubstituteRequest, | ||
| } from '@/features/manager/api/substitute' | ||
| import type { ManagerSubstituteListFilters } from '@/features/manager/substitute/lib/managerSubstituteListFilters' | ||
| import type { SubstituteListStatusFilter } from '@/features/user/substitute/lib/substituteListFilters' | ||
|
Comment on lines
+10
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win 필터 타입을
As per path instructions, “src/features/** … entities, shared 레이어만 import하는지”를 확인해야 합니다. 🤖 Prompt for AI AgentsSource: Path instructions |
||
| import type { SubstituteRequestItem } from '@/shared/types/substituteRequest' | ||
| import type { SubstituteActionType } from '@/pages/manager/substitute-request/components/ManagerSubstituteActionModal' | ||
| import { SubstituteApiStatus } from '@/shared/types/substituteStatus' | ||
| import { queryKeys } from '@/shared/lib/queryKeys' | ||
|
|
||
| const SUBSTITUTE_ACTION_ERROR_MESSAGES: Record<string, string> = { | ||
| B001: '이미 처리되었거나 승인/거절할 수 없는 상태입니다.', | ||
|
|
@@ -30,8 +31,28 @@ function getSubstituteActionErrorMessage(error: unknown): string { | |
|
|
||
| type ActionTarget = { id: number; type: SubstituteActionType } | ||
|
|
||
| export type ManagerSubstituteSectionKey = 'pending' | 'accepted' | 'cancelled' | ||
|
|
||
| export type ManagerSubstituteSection = { | ||
| key: ManagerSubstituteSectionKey | ||
| title: string | ||
| items: SubstituteRequestItem[] | ||
| } | ||
|
|
||
| const EMPTY_GROUPS = { pending: [], accepted: [], cancelled: [] } | ||
|
|
||
| const SECTION_TITLE: Record<ManagerSubstituteSectionKey, string> = { | ||
| pending: '요청됨', | ||
| accepted: '수락됨', | ||
| cancelled: '취소됨', | ||
| } | ||
|
|
||
| const SECTION_ORDER: ManagerSubstituteSectionKey[] = [ | ||
| 'pending', | ||
| 'accepted', | ||
| 'cancelled', | ||
| ] | ||
|
|
||
| // ACCEPTED = 워커가 수락해 사장 승인 대기 중 → 요청됨 | ||
| // APPROVED = 사장이 승인 → 수락됨 | ||
| // REJECTED_BY_APPROVER = 사장이 거절 → 취소됨 | ||
|
|
@@ -53,24 +74,49 @@ function groupByStatus(requests: SubstituteRequestItem[]) { | |
| return { pending, accepted, cancelled } | ||
| } | ||
|
|
||
| export function useManagerSubstituteRequestViewModel() { | ||
| function buildSections( | ||
| groups: ReturnType<typeof groupByStatus>, | ||
| statusFilter: SubstituteListStatusFilter | ||
| ): ManagerSubstituteSection[] { | ||
| const allSections = SECTION_ORDER.map(key => ({ | ||
| key, | ||
| title: SECTION_TITLE[key], | ||
| items: groups[key], | ||
| })) | ||
|
|
||
| if (statusFilter !== 'all') { | ||
| return allSections.filter( | ||
| section => section.key === statusFilter && section.items.length > 0 | ||
| ) | ||
| } | ||
|
|
||
| return allSections.filter(section => section.items.length > 0) | ||
| } | ||
|
|
||
| export function useManagerSubstituteRequestViewModel( | ||
| filters: ManagerSubstituteListFilters = { statusFilter: 'all' } | ||
| ) { | ||
| const queryClient = useQueryClient() | ||
| const { activeWorkspaceId } = useManagedWorkspacesQuery() | ||
| const { requests, isLoading, isError } = | ||
| useSubstituteRequestsViewModel(activeWorkspaceId) | ||
| const { statusFilter } = filters | ||
| const { requests, isLoading, isError } = useSubstituteRequestsViewModel( | ||
| activeWorkspaceId, | ||
| { statusFilter } | ||
| ) | ||
| const [actionTarget, setActionTarget] = useState<ActionTarget | null>(null) | ||
| const [actionError, setActionError] = useState<string | null>(null) | ||
|
|
||
| const invalidate = () => | ||
| queryClient.invalidateQueries({ | ||
| queryKey: queryKeys.substitute.list({ | ||
| workspaceId: activeWorkspaceId ?? undefined, | ||
| }), | ||
| queryKey: ['substitute', 'list'], | ||
| }) | ||
|
|
||
| const approveMutation = useMutation({ | ||
| mutationFn: ({ id, comment }: { id: number; comment: string }) => | ||
| approveSubstituteRequest(id, { approvalComment: comment }), | ||
| approveSubstituteRequest( | ||
| id, | ||
| comment !== '' ? { approvalComment: comment } : {} | ||
| ), | ||
| onSuccess: async () => { | ||
| await invalidate() | ||
| setActionTarget(null) | ||
|
|
@@ -94,10 +140,10 @@ export function useManagerSubstituteRequestViewModel() { | |
| }, | ||
| }) | ||
|
|
||
| const { pending, accepted, cancelled } = useMemo( | ||
| () => groupByStatus(requests), | ||
| [requests] | ||
| ) | ||
| const sections = useMemo(() => { | ||
| const groups = groupByStatus(requests) | ||
| return buildSections(groups, statusFilter) | ||
| }, [requests, statusFilter]) | ||
|
|
||
| const handleModalSubmit = (comment: string) => { | ||
| if (actionTarget === null) return | ||
|
|
@@ -117,10 +163,8 @@ export function useManagerSubstituteRequestViewModel() { | |
| return { | ||
| isLoading, | ||
| isError, | ||
| isEmpty: requests.length === 0, | ||
| pending, | ||
| accepted, | ||
| cancelled, | ||
| isEmpty: sections.length === 0, | ||
| sections, | ||
| actionsDisabled: approveMutation.isPending || rejectMutation.isPending, | ||
| actionModal: { | ||
| open: actionTarget !== null, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,23 @@ | ||||||
| import type { SubstituteListStatusFilter } from '@/features/user/substitute/lib/substituteListFilters' | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win 매니저 feature가 user feature 타입에 직접 의존하고 있습니다.
수정 예시(단기 완화안)-import type { SubstituteListStatusFilter } from '`@/features/user/substitute/lib/substituteListFilters`'
import { SubstituteApiStatus } from '`@/shared/types/substituteStatus`'
+type SubstituteListStatusFilter = 'all' | 'pending' | 'accepted' | 'cancelled'
+
export type ManagerSubstituteListFilters = {
statusFilter: SubstituteListStatusFilter
}As per path instructions, " 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Path instructions |
||||||
| import { SubstituteApiStatus } from '@/shared/types/substituteStatus' | ||||||
|
|
||||||
| export type ManagerSubstituteListFilters = { | ||||||
| statusFilter: SubstituteListStatusFilter | ||||||
| } | ||||||
|
|
||||||
| /** 매니저 UI 필터 → API status (G5 기준) */ | ||||||
| export const MANAGER_FILTER_TO_API_STATUS: Record< | ||||||
| SubstituteListStatusFilter, | ||||||
| SubstituteApiStatus[] | ||||||
| > = { | ||||||
| all: [], | ||||||
| pending: [SubstituteApiStatus.ACCEPTED], | ||||||
| accepted: [SubstituteApiStatus.APPROVED], | ||||||
| cancelled: [SubstituteApiStatus.REJECTED_BY_APPROVER], | ||||||
| } | ||||||
|
|
||||||
| export function resolveManagerApiStatuses( | ||||||
| filter: SubstituteListStatusFilter | ||||||
| ): SubstituteApiStatus[] { | ||||||
| return MANAGER_FILTER_TO_API_STATUS[filter] | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
Repository: alter-app/alter-client
Length of output: 249
🏁 Script executed:
Repository: alter-app/alter-client
Length of output: 229
🏁 Script executed:
Repository: alter-app/alter-client
Length of output: 229
@tanstack/react-query와 Devtools 버전을 맞춰주세요.@tanstack/react-query-devtools@5.101.1은@tanstack/react-query@^5.101.1을 요구하므로, 현재^5.90.21과 peer 조건이 맞지 않습니다.react-query를^5.101.1로 올리거나 Devtools를 같은 릴리스 라인으로 내리세요.🤖 Prompt for AI Agents