Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/apis/external/useGetExternalDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { axiosInstance } from '../axios.ts';
import { useQuery } from '@tanstack/react-query';
import { queryKey } from '../../constants/queryKey.ts';
import type { ResponseViewExternalDetailDto, ViewExternalDetailDto } from '../../types/external.ts';

/**
* 외부이슈 상세 조회 함수
* - 외부이슈 상세페이지 조회 모드에서 사용
* - pages/external/ExternalDetail.tsx
* - pages/workspace/WorkspaceExternalDetail.tsx
*/
const getExternalDetail = async (
teamId: number,
externalId: number
): Promise<ViewExternalDetailDto> => {
try {
const { data } = await axiosInstance.get<ResponseViewExternalDetailDto>(
`/api/teams/${teamId}/externals/${externalId}`
);
if (!data.result) return Promise.reject(data);
if (data?.isSuccess) {
console.log('조회 성공:', data.result);
}
return data.result;
} catch (error) {
console.error('외부이슈 상세 조회 실패', error);
throw error;
}
};

export const useGetExternalDetail = (
teamId: number,
externalId: number,
opts?: { enabled?: boolean }
) => {
const enabled = (opts?.enabled ?? true) && Number.isFinite(externalId) && externalId > 0;

return useQuery<ViewExternalDetailDto>({
queryKey: [queryKey.EXTERNAL_DETAIL, externalId],
queryFn: () => getExternalDetail(teamId, externalId),
enabled, // ← create 경로 등에서 NaN/0이면 쿼리 미실행
retry: (failureCount, error: any) => {
if (error?.response?.status === 404) return false; // 404면 재시도 안함
return failureCount < 2;
},
});
};
5 changes: 3 additions & 2 deletions src/apis/external/useGetGithubInstallationId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface GetGithubInstallationIdResponse {
}

// 깃허브 설치 ID 조회
const getGithubInstallationId = async (
export const getGithubInstallationId = async (
teamId: number
): Promise<GetGithubInstallationIdResponse> => {
try {
Expand All @@ -24,9 +24,10 @@ const getGithubInstallationId = async (
}
};

export const useGetGithubInstallationId = (teamId: number) => {
export const useGetGithubInstallationId = (teamId: number, opts?: { enabled?: boolean }) => {
return useQuery({
queryKey: [queryKey.GITHUB_INSTALLATION_ID, teamId],
queryFn: () => getGithubInstallationId(teamId),
enabled: opts?.enabled ?? true,
});
};
5 changes: 3 additions & 2 deletions src/apis/external/useGetGithubRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface GetGithubRepositoryResponse {
}

// 깃허브 레포지토리 조회
const getGithubRepository = async (teamId: number): Promise<GetGithubRepositoryResponse> => {
export const getGithubRepository = async (teamId: number): Promise<GetGithubRepositoryResponse> => {
try {
const response = await axiosInstance.get<CommonResponse<GetGithubRepositoryResponse>>(
`/api/teams/${teamId}/github/repositories`
Expand All @@ -32,9 +32,10 @@ const getGithubRepository = async (teamId: number): Promise<GetGithubRepositoryR
}
};

export const useGetGithubRepository = (teamId: number) => {
export const useGetGithubRepository = (teamId: number, opts?: { enabled?: boolean }) => {
return useQuery({
queryKey: [queryKey.GITHUB_REPOSITORIES, teamId],
queryFn: () => getGithubRepository(teamId),
enabled: opts?.enabled ?? true,
});
};
49 changes: 49 additions & 0 deletions src/apis/external/usePatchExternalDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { axiosInstance } from '../axios.ts';
import { useMutation } from '@tanstack/react-query';
import { mutationKey } from '../../constants/mutationKey.ts';
import { queryKey } from '../../constants/queryKey.ts';
import queryClient from '../../utils/queryClient.ts';
import type {
ResponseUpdateExternalDetailDto,
UpdateExternalDetailDto,
UpdateExternalResultDto,
} from '../../types/external.ts';

/**
* 외부이슈 수정 (PATCH) 함수
* - 동일 teamId / 동일 externalId 대상의 상세 내용 반영
* - pages/external/ExternalDetail.tsx
* - pages/workspace/WorkspaceExternalDetail.tsx
*/
const updateExternal = async (
teamId: number,
externalId: number,
payload: UpdateExternalDetailDto
): Promise<UpdateExternalResultDto> => {
try {
const response = await axiosInstance.patch<ResponseUpdateExternalDetailDto>(
`/api/teams/${teamId}/externals/${externalId}`,
payload
);

if (!response.data.result) return Promise.reject(response);
return response.data.result;
} catch (error: any) {
console.error('외부이슈 수정 실패:', error);
console.log('👉 RESPONSE STATUS:', error?.response?.status);
console.log('👉 RESPONSE DATA:', error?.response?.data);
throw error;
}
};

export const useUpdateExternal = (teamId: number, externalId: number) => {
return useMutation<UpdateExternalResultDto, Error, UpdateExternalDetailDto>({
mutationKey: [mutationKey.EXTERNAL_UPDATE, teamId, externalId],
mutationFn: (payload) => updateExternal(teamId, externalId, payload),
onSuccess: () => {
// 상세/목록/관련 파생 쿼리 최신화
queryClient.invalidateQueries({ queryKey: [queryKey.EXTERNAL_LIST, teamId] });
queryClient.invalidateQueries({ queryKey: [queryKey.EXTERNAL_NAME, teamId] });
},
});
};
48 changes: 48 additions & 0 deletions src/apis/external/usePostCreateExternalDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { axiosInstance } from '../axios.ts';
import { useMutation } from '@tanstack/react-query';
import { mutationKey } from '../../constants/mutationKey.ts';
import { queryKey } from '../../constants/queryKey.ts';
import queryClient from '../../utils/queryClient.ts';
import type {
CreateExternalDetailDto,
CreateExternalResultDto,
ResponseCreateExternalDetatilDto,
} from '../../types/external.ts';

/**
* 외부이슈 작성 함수
* - 외부이슈 상세페이지 생성 모드에서 사용
* - pages/external/ExternalDetail.tsx
* - pages/workspace/WorkspaceExternalDetail.tsx
*/
const createExternal = async (
teamId: number,
payload: CreateExternalDetailDto
): Promise<CreateExternalResultDto> => {
try {
const response = await axiosInstance.post<ResponseCreateExternalDetatilDto>(
`/api/teams/${teamId}/externals`,
payload
);

if (!response.data.result) return Promise.reject(response);
return response.data.result;
} catch (error: any) {
console.error('외부이슈 작성 실패:', error);
console.log('👉 RESPONSE STATUS:', error?.response?.status);
console.log('👉 RESPONSE DATA:', error?.response?.data);
throw error;
}
};

export const useCreateExternal = (teamId: number) => {
return useMutation<CreateExternalResultDto, Error, CreateExternalDetailDto>({
mutationKey: [mutationKey.EXTERNAL_CREATE, teamId],
mutationFn: (payload) => createExternal(teamId, payload),
onSuccess: () => {
// 외부이슈 작성하여 POST 후 조회되는 데이터 최신화
queryClient.invalidateQueries({ queryKey: [queryKey.EXTERNAL_LIST, teamId] });
queryClient.invalidateQueries({ queryKey: [queryKey.EXTERNAL_NAME, teamId] });
},
});
};
94 changes: 94 additions & 0 deletions src/hooks/useExternalDeadlinePatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { useEffect, useRef, useCallback } from 'react';
import { buildDeadlinePatch } from '../utils/deadlinePatch';
import type { UpdateExternalDetailDto } from '../types/external';

type UseExternalDeadlinePatchParams = {
externalDetail: any;
isViewMode: boolean;
canPatch: boolean;
mutateUpdate: (payload: UpdateExternalDetailDto, opts?: { onSuccess?: () => void }) => void;
};

/**
* - externalDetail의 기존 deadline(start/end)을 기억
* - 달력 onSelect / edit 제출 시 변경분만 계산해 PATCH 전송
*/
export function useExternalDeadlinePatch({
externalDetail,
isViewMode,
canPatch,
mutateUpdate,
}: UseExternalDeadlinePatchParams) {
const originalDeadlineRef = useRef<{ start: string | null; end: string | null }>({
start: null,
end: null,
});

// externalDetail 변경 시 원본 저장
useEffect(() => {
const prevStart =
externalDetail?.deadline?.start && typeof externalDetail.deadline.start === 'string'
? externalDetail.deadline.start
: null;
const prevEnd =
externalDetail?.deadline?.end && typeof externalDetail.deadline.end === 'string'
? externalDetail.deadline.end
: null;
originalDeadlineRef.current = { start: prevStart, end: prevEnd };
}, [externalDetail]);

/** view 모드에서 달력 선택 → 즉시 PATCH */
const handleSelectDateAndPatch = useCallback(
(date: [Date | null, Date | null]) => {
if (!isViewMode || !canPatch) return;

const [nextStart, nextEnd] = date;
const patch = buildDeadlinePatch(
originalDeadlineRef.current.start,
originalDeadlineRef.current.end,
nextStart,
nextEnd
);

if (!patch) return;

mutateUpdate(patch, {
onSuccess: () => {
// 전송 성공 시 원본 갱신
const d = patch.deadline;
originalDeadlineRef.current = {
start:
d.start !== undefined
? d.start === 'null'
? null
: d.start
: originalDeadlineRef.current.start,
end:
d.end !== undefined
? d.end === 'null'
? null
: d.end
: originalDeadlineRef.current.end,
};
},
});
},
[isViewMode, canPatch, mutateUpdate]
);

/** edit 모드에서 '작성 완료' 시 선택된 날짜로 PATCH 조각 생성 */
const buildPatchForEditSubmit = useCallback((date: [Date | null, Date | null]) => {
const [nextStart, nextEnd] = date;
return buildDeadlinePatch(
originalDeadlineRef.current.start,
originalDeadlineRef.current.end,
nextStart,
nextEnd
);
}, []);

return {
handleSelectDateAndPatch,
buildPatchForEditSubmit,
};
}
Loading