diff --git a/backend/src/modules/task/task.service.ts b/backend/src/modules/task/task.service.ts index 73a93da..ce3dc87 100644 --- a/backend/src/modules/task/task.service.ts +++ b/backend/src/modules/task/task.service.ts @@ -216,7 +216,12 @@ export class TaskService { ); } - qb.orderBy('task.seq', 'DESC').addOrderBy('checklist.sort_order', 'ASC'); + // 정렬은 메인 엔티티(task.seq) 기준만 사용한다. + // skip/take(페이지네이션)와 to-many 조인을 함께 쓰면 TypeORM 이 DISTINCT id 서브쿼리로 + // 페이징하는데, 이때 조인된 to-many 컬럼(checklist.sort_order)으로 ORDER BY 하면 + // 컬럼 메타데이터를 찾지 못해 터진다(createOrderByCombinedWithSelectExpression). + // 목록 응답은 체크리스트의 [완료/전체] 수만 쓰므로 항목 순서가 필요 없다. + qb.orderBy('task.seq', 'DESC'); const [tasks, total] = await qb .skip(pg.skip) diff --git a/frontend/src/composables/useMember.ts b/frontend/src/composables/useMember.ts index 1ad7f75..f64b6b5 100644 --- a/frontend/src/composables/useMember.ts +++ b/frontend/src/composables/useMember.ts @@ -4,15 +4,22 @@ import type { InviteMemberPayload, UpdateMemberPayload, } from '@/types/member' +import type { Paginated } from '@/types/pagination' // 저장소 멤버 도메인 API Composable — 인터셉터가 success/data 를 언래핑 // 경로는 모두 repoId(slugName) 하위: /repos/:repoId/members export function useMember() { const api = useApi() - // 멤버 목록 (멤버 또는 공개 저장소) - async function list(repoId: string): Promise { - return (await api.get(`/repos/${repoId}/members`)) as unknown as ApiRepoMember[] + // 멤버 목록 (멤버 또는 공개 저장소) — 백엔드가 페이지네이션 { items, total, ... } 반환 + async function list( + repoId: string, + page = 1, + size = 20, + ): Promise> { + return (await api.get(`/repos/${repoId}/members`, { + params: { page, size }, + })) as unknown as Paginated } // 멤버 초대 (admin) — 가입된 사용자만 이메일로 초대 diff --git a/frontend/src/composables/useTask.ts b/frontend/src/composables/useTask.ts index f3e5fdc..ceac95c 100644 --- a/frontend/src/composables/useTask.ts +++ b/frontend/src/composables/useTask.ts @@ -10,17 +10,21 @@ import type { UpdateTaskPayload, } from '@/types/task' import type { TaskStatus } from '@/mock/relay.mock' +import type { Paginated } from '@/types/pagination' // 업무 도메인 API Composable — 인터셉터가 success/data 를 언래핑 // 경로는 모두 repoId(slugName) 하위: /repos/:repoId/tasks[/:taskId] export function useTask() { const api = useApi() - // 업무 목록 (상태/담당자 필터 선택) - async function list(repoId: string, query: TaskListQuery = {}): Promise { + // 업무 목록 (상태/담당자 필터 선택) — 백엔드가 페이지네이션 { items, total, ... } 반환 + async function list( + repoId: string, + query: TaskListQuery = {}, + ): Promise> { return (await api.get(`/repos/${repoId}/tasks`, { params: query, - })) as unknown as ApiRepoTask[] + })) as unknown as Paginated } // 업무 단건 상세 (taskId = 저장소 내 순번) diff --git a/frontend/src/stores/member.store.ts b/frontend/src/stores/member.store.ts index 9b70e1a..17037ec 100644 --- a/frontend/src/stores/member.store.ts +++ b/frontend/src/stores/member.store.ts @@ -45,8 +45,8 @@ export const useMemberStore = defineStore('member', () => { async function fetchList(repoId: string): Promise { loading.value = true try { - const list = await memberApi.list(repoId) - members.value = list.map((m) => toMemberView(m, currentUserId())) + const { items } = await memberApi.list(repoId) + members.value = items.map((m) => toMemberView(m, currentUserId())) } finally { loading.value = false } diff --git a/frontend/src/stores/task.store.ts b/frontend/src/stores/task.store.ts index ed91909..1c7b3fb 100644 --- a/frontend/src/stores/task.store.ts +++ b/frontend/src/stores/task.store.ts @@ -211,8 +211,8 @@ export const useTaskStore = defineStore('task', () => { async function fetchList(repoId: string, query: TaskListQuery = {}): Promise { loading.value = true try { - const list = await taskApi.list(repoId, query) - tasks.value = list.map(toRepoTaskView) + const { items } = await taskApi.list(repoId, query) + tasks.value = items.map(toRepoTaskView) } finally { loading.value = false }