feat: 업무 상세 부속·활동 피드·내 업무 집계 + 페이지네이션 (5~8단계)
- 5단계(업무 상세 부속): 체크리스트(상세 읽기전용/수정폼 추가·삭제)·댓글/답글·파일 첨부(서버 로컬 업로드 폴더)·승인 워크플로우(요청/승인/수정요청 노트) - 6단계(활동 피드): 저장소/멤버/업무 행위를 이벤트로 자동 적재, 저장소 활동 피드·업무 활동 탭 실연동(프론트가 type+payload로 문구 조립) - 7단계(내 업무): 담당/지시 저장소 횡단 집계, MyTasksPage 실연동, TaskAssigneePage(/view) 제거 - 8단계(진행): 목록 페이지네이션 통일 — 백엔드 전체(공통 유틸 + 5개 목록, 저장소 업무는 세그먼트/검색/카운트) + 프론트 더보기(저장소 목록·내 업무 완료, 멤버·저장소 업무 대기) - UI 보강: 업무 목록 댓글/첨부 분리표기, 첨부 배지 색상·라벨 통일, 완료 업무 D-Day 배지, 활동 이벤트 점검·보강(업무 수정/삭제·답글·첨부 삭제) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import { useApi } from './useApi'
|
||||
import type { ApiActivity } from '@/types/activity'
|
||||
|
||||
// 활동 피드 API Composable — 인터셉터가 success/data 를 언래핑
|
||||
export function useActivity() {
|
||||
const api = useApi()
|
||||
|
||||
// 저장소 활동 피드(최신순)
|
||||
async function list(repoId: string): Promise<ApiActivity[]> {
|
||||
return (await api.get(`/repos/${repoId}/activity`)) as unknown as ApiActivity[]
|
||||
}
|
||||
|
||||
return { list }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useApi } from './useApi'
|
||||
import type { ApiMyTaskRow } from '@/types/my-task'
|
||||
import type { Paginated } from '@/types/pagination'
|
||||
|
||||
// 내 업무 API Composable — 저장소 횡단 집계(담당/지시), 오프셋 페이지네이션
|
||||
export function useMyTask() {
|
||||
const api = useApi()
|
||||
|
||||
// 내가 담당인 업무
|
||||
async function assigned(page = 1, size = 20): Promise<Paginated<ApiMyTaskRow>> {
|
||||
return (await api.get('/tasks/assigned', {
|
||||
params: { page, size },
|
||||
})) as unknown as Paginated<ApiMyTaskRow>
|
||||
}
|
||||
|
||||
// 내가 지시한 업무
|
||||
async function issued(page = 1, size = 20): Promise<Paginated<ApiMyTaskRow>> {
|
||||
return (await api.get('/tasks/issued', {
|
||||
params: { page, size },
|
||||
})) as unknown as Paginated<ApiMyTaskRow>
|
||||
}
|
||||
|
||||
return { assigned, issued }
|
||||
}
|
||||
@@ -5,13 +5,16 @@ import type {
|
||||
RepoCreateMeta,
|
||||
UpdateRepoPayload,
|
||||
} from '@/types/repo'
|
||||
import type { Paginated } from '@/types/pagination'
|
||||
|
||||
// 저장소 도메인 API Composable — 인터셉터가 success/data 를 언래핑
|
||||
export function useRepo() {
|
||||
const api = useApi()
|
||||
|
||||
async function list(): Promise<ApiRepo[]> {
|
||||
return (await api.get('/repos')) as unknown as ApiRepo[]
|
||||
async function list(page = 1, size = 20): Promise<Paginated<ApiRepo>> {
|
||||
return (await api.get('/repos', {
|
||||
params: { page, size },
|
||||
})) as unknown as Paginated<ApiRepo>
|
||||
}
|
||||
|
||||
// 저장소 생성 폼 메타(소유자/템플릿) — 생성 전 미리 조회
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { useApi } from './useApi'
|
||||
import type {
|
||||
ApiAttachment,
|
||||
ApiComment,
|
||||
ApiCommentReply,
|
||||
ApiRepoTask,
|
||||
ApiTaskDetail,
|
||||
CreateTaskPayload,
|
||||
@@ -58,5 +61,98 @@ export function useTask() {
|
||||
})) as unknown as ApiTaskDetail
|
||||
}
|
||||
|
||||
return { list, get, create, update, remove, changeStatus }
|
||||
/* ===================== 댓글 / 답글 (5단계) ===================== */
|
||||
|
||||
async function addComment(
|
||||
repoId: string,
|
||||
taskId: number,
|
||||
text: string,
|
||||
fileId?: string,
|
||||
): Promise<ApiComment> {
|
||||
return (await api.post(`/repos/${repoId}/tasks/${taskId}/comments`, {
|
||||
text,
|
||||
...(fileId ? { fileId } : {}),
|
||||
})) as unknown as ApiComment
|
||||
}
|
||||
|
||||
async function addReply(
|
||||
repoId: string,
|
||||
taskId: number,
|
||||
commentId: string,
|
||||
text: string,
|
||||
): Promise<ApiCommentReply> {
|
||||
return (await api.post(
|
||||
`/repos/${repoId}/tasks/${taskId}/comments/${commentId}/replies`,
|
||||
{ text },
|
||||
)) as unknown as ApiCommentReply
|
||||
}
|
||||
|
||||
/* ===================== 파일 첨부 (5단계) ===================== */
|
||||
|
||||
async function uploadFile(
|
||||
repoId: string,
|
||||
taskId: number,
|
||||
file: File,
|
||||
): Promise<ApiAttachment> {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
return (await api.post(`/repos/${repoId}/tasks/${taskId}/files`, form, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})) as unknown as ApiAttachment
|
||||
}
|
||||
|
||||
async function removeFile(
|
||||
repoId: string,
|
||||
taskId: number,
|
||||
fileId: string,
|
||||
): Promise<void> {
|
||||
await api.delete(`/repos/${repoId}/tasks/${taskId}/files/${fileId}`)
|
||||
}
|
||||
|
||||
/* ===================== 승인 워크플로우 (5단계) ===================== */
|
||||
|
||||
async function requestApproval(
|
||||
repoId: string,
|
||||
taskId: number,
|
||||
note?: string,
|
||||
): Promise<ApiTaskDetail> {
|
||||
return (await api.post(`/repos/${repoId}/tasks/${taskId}/approval/request`, {
|
||||
...(note ? { note } : {}),
|
||||
})) as unknown as ApiTaskDetail
|
||||
}
|
||||
|
||||
async function approve(
|
||||
repoId: string,
|
||||
taskId: number,
|
||||
): Promise<ApiTaskDetail> {
|
||||
return (await api.post(
|
||||
`/repos/${repoId}/tasks/${taskId}/approval/approve`,
|
||||
)) as unknown as ApiTaskDetail
|
||||
}
|
||||
|
||||
async function requestChanges(
|
||||
repoId: string,
|
||||
taskId: number,
|
||||
note: string,
|
||||
): Promise<ApiTaskDetail> {
|
||||
return (await api.post(`/repos/${repoId}/tasks/${taskId}/approval/changes`, {
|
||||
note,
|
||||
})) as unknown as ApiTaskDetail
|
||||
}
|
||||
|
||||
return {
|
||||
list,
|
||||
get,
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
changeStatus,
|
||||
addComment,
|
||||
addReply,
|
||||
uploadFile,
|
||||
removeFile,
|
||||
requestApproval,
|
||||
approve,
|
||||
requestChanges,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user