c09710f4e5
Departments are now a customizable per-project list (standard + custom), matching the existing custom task type/status pattern, instead of a fixed 7-value enum. They're usable directly on tasks (new field, independent of assignee) and continue to drive team member department roles.
316 lines
8.7 KiB
TypeScript
316 lines
8.7 KiB
TypeScript
import { apiClient } from './api'
|
|
import { TaskStatus } from './asset'
|
|
|
|
export interface TaskStatusUpdate {
|
|
status: TaskStatus
|
|
}
|
|
|
|
export interface Task {
|
|
id: number
|
|
name: string
|
|
description?: string
|
|
task_type: string
|
|
status: TaskStatus
|
|
department?: string
|
|
start_date?: string
|
|
deadline?: string
|
|
project_id: number
|
|
project_name?: string
|
|
episode_id?: number
|
|
episode_name?: string
|
|
shot_id?: number
|
|
shot_name?: string
|
|
asset_id?: number
|
|
asset_name?: string
|
|
assigned_user_id?: number
|
|
assigned_user_name?: string
|
|
assigned_user_email?: string
|
|
assigned_user_avatar_url?: string | null
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
export interface TaskListItem {
|
|
id: number
|
|
name: string
|
|
task_type: string
|
|
status: TaskStatus
|
|
department?: string
|
|
start_date?: string
|
|
deadline?: string
|
|
project_id: number
|
|
project_name: string
|
|
episode_id?: number
|
|
episode_name?: string
|
|
shot_id?: number
|
|
shot_name?: string
|
|
asset_id?: number
|
|
asset_name?: string
|
|
assigned_user_id?: number
|
|
assigned_user_name?: string
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
export interface TaskStatusInfo {
|
|
task_type: string
|
|
status: TaskStatus
|
|
task_id?: number
|
|
assigned_user_id?: number
|
|
}
|
|
|
|
export type NoteType = 'internal' | 'client'
|
|
|
|
export interface ProductionNote {
|
|
id: number
|
|
content: string
|
|
note_type: NoteType
|
|
parent_note_id?: number
|
|
task_id: number
|
|
user_id: number
|
|
created_at: string
|
|
updated_at: string
|
|
user_first_name: string
|
|
user_last_name: string
|
|
user_email: string
|
|
user_avatar_url?: string | null
|
|
child_notes?: ProductionNote[]
|
|
}
|
|
|
|
export interface TaskAttachment {
|
|
id: number
|
|
task_id: number
|
|
user_id: number
|
|
file_name: string
|
|
file_path: string
|
|
file_type: string
|
|
file_size: number
|
|
attachment_type: string
|
|
description?: string
|
|
uploaded_at: string
|
|
user_first_name: string
|
|
user_last_name: string
|
|
download_url?: string
|
|
thumbnail_url?: string
|
|
}
|
|
|
|
export interface Submission {
|
|
id: number
|
|
task_id: number
|
|
user_id: number
|
|
file_path: string
|
|
file_name: string
|
|
version_number: number
|
|
notes?: string
|
|
submitted_at: string
|
|
user_first_name: string
|
|
user_last_name: string
|
|
latest_review?: any
|
|
download_url?: string
|
|
thumbnail_url?: string
|
|
stream_url?: string
|
|
}
|
|
|
|
export interface SubmissionDateInfo {
|
|
task_id: number
|
|
submitted_at: string
|
|
}
|
|
|
|
export interface TaskFilters {
|
|
projectId?: number
|
|
shotId?: number
|
|
assetId?: number
|
|
assignedUserId?: number
|
|
status?: string
|
|
taskType?: string
|
|
departmentRole?: string
|
|
limit?: number
|
|
}
|
|
|
|
export interface BulkStatusUpdateRequest {
|
|
task_ids: number[]
|
|
status: string
|
|
}
|
|
|
|
export interface BulkAssignmentRequest {
|
|
task_ids: number[]
|
|
assigned_user_id: number
|
|
}
|
|
|
|
export interface BulkActionResult {
|
|
success_count: number
|
|
failed_count: number
|
|
errors?: Array<{ task_id: number; error: string }>
|
|
}
|
|
|
|
class TaskService {
|
|
async getTasks(filters?: TaskFilters): Promise<TaskListItem[]> {
|
|
const params = new URLSearchParams()
|
|
if (filters?.projectId) params.append('project_id', filters.projectId.toString())
|
|
if (filters?.shotId) params.append('shot_id', filters.shotId.toString())
|
|
if (filters?.assetId) params.append('asset_id', filters.assetId.toString())
|
|
if (filters?.assignedUserId) params.append('assigned_user_id', filters.assignedUserId.toString())
|
|
if (filters?.status) params.append('status', filters.status)
|
|
if (filters?.taskType) params.append('task_type', filters.taskType)
|
|
if (filters?.departmentRole) params.append('department_role', filters.departmentRole)
|
|
if (filters?.limit) params.append('limit', filters.limit.toString())
|
|
|
|
const response = await apiClient.get(`/tasks/?${params}`)
|
|
return response.data
|
|
}
|
|
|
|
async getMyTasks(projectId?: number, status?: string): Promise<TaskListItem[]> {
|
|
const params = new URLSearchParams()
|
|
if (projectId) params.append('project_id', projectId.toString())
|
|
if (status) params.append('status', status)
|
|
|
|
const response = await apiClient.get(`/tasks/my-tasks?${params}`)
|
|
return response.data
|
|
}
|
|
|
|
async getTask(taskId: number): Promise<Task> {
|
|
const response = await apiClient.get(`/tasks/${taskId}`)
|
|
return response.data
|
|
}
|
|
|
|
async updateTaskStatus(taskId: number, status: TaskStatus): Promise<Task> {
|
|
const response = await apiClient.put(`/tasks/${taskId}/status`, {
|
|
status
|
|
})
|
|
return response.data
|
|
}
|
|
|
|
async updateTask(taskId: number, updates: Partial<Task>): Promise<Task> {
|
|
const response = await apiClient.put(`/tasks/${taskId}`, updates)
|
|
return response.data
|
|
}
|
|
|
|
async assignTask(taskId: number, assignedUserId: number): Promise<Task> {
|
|
const response = await apiClient.put(`/tasks/${taskId}/assign`, {
|
|
assigned_user_id: assignedUserId
|
|
})
|
|
return response.data
|
|
}
|
|
|
|
// Notes
|
|
async getTaskNotes(taskId: number): Promise<ProductionNote[]> {
|
|
const response = await apiClient.get(`/tasks/${taskId}/notes`)
|
|
return response.data
|
|
}
|
|
|
|
async createTaskNote(taskId: number, content: string, parentNoteId?: number, noteType: NoteType = 'internal'): Promise<ProductionNote> {
|
|
const response = await apiClient.post(`/tasks/${taskId}/notes`, {
|
|
content,
|
|
parent_note_id: parentNoteId,
|
|
note_type: noteType
|
|
})
|
|
return response.data
|
|
}
|
|
|
|
async updateTaskNote(taskId: number, noteId: number, content: string): Promise<ProductionNote> {
|
|
const response = await apiClient.put(`/tasks/${taskId}/notes/${noteId}`, {
|
|
content
|
|
})
|
|
return response.data
|
|
}
|
|
|
|
async deleteTaskNote(taskId: number, noteId: number): Promise<void> {
|
|
await apiClient.delete(`/tasks/${taskId}/notes/${noteId}`)
|
|
}
|
|
|
|
// Attachments
|
|
async getTaskAttachments(taskId: number, attachmentType?: string): Promise<TaskAttachment[]> {
|
|
const params = attachmentType ? `?attachment_type=${attachmentType}` : ''
|
|
const response = await apiClient.get(`/tasks/${taskId}/attachments${params}`)
|
|
return response.data
|
|
}
|
|
|
|
async uploadTaskAttachment(
|
|
taskId: number,
|
|
file: File,
|
|
attachmentType: string = 'reference',
|
|
description?: string
|
|
): Promise<TaskAttachment> {
|
|
const formData = new FormData()
|
|
formData.append('file', file)
|
|
formData.append('attachment_type', attachmentType)
|
|
if (description) {
|
|
formData.append('description', description)
|
|
}
|
|
|
|
const response = await apiClient.post(`/tasks/${taskId}/attachments`, formData, {
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data'
|
|
}
|
|
})
|
|
return response.data
|
|
}
|
|
|
|
async deleteTaskAttachment(taskId: number, attachmentId: number): Promise<void> {
|
|
await apiClient.delete(`/tasks/${taskId}/attachments/${attachmentId}`)
|
|
}
|
|
|
|
// Submissions
|
|
async getTaskSubmissions(taskId: number): Promise<Submission[]> {
|
|
const response = await apiClient.get(`/tasks/${taskId}/submissions`)
|
|
return response.data
|
|
}
|
|
|
|
async getSubmissionDates(projectId: number): Promise<SubmissionDateInfo[]> {
|
|
const response = await apiClient.get(`/tasks/submission-dates?project_id=${projectId}`)
|
|
return response.data
|
|
}
|
|
|
|
async submitWork(taskId: number, file: File, notes?: string): Promise<Submission> {
|
|
const formData = new FormData()
|
|
formData.append('file', file)
|
|
if (notes) {
|
|
formData.append('notes', notes)
|
|
}
|
|
|
|
const response = await apiClient.post(`/tasks/${taskId}/submit`, formData, {
|
|
headers: {
|
|
'Content-Type': 'multipart/form-data'
|
|
}
|
|
})
|
|
return response.data
|
|
}
|
|
|
|
async updateSubmission(taskId: number, submissionId: number, notes: string): Promise<Submission> {
|
|
const response = await apiClient.put(`/tasks/${taskId}/submissions/${submissionId}`, { notes })
|
|
return response.data
|
|
}
|
|
|
|
async deleteSubmission(taskId: number, submissionId: number): Promise<void> {
|
|
await apiClient.delete(`/tasks/${taskId}/submissions/${submissionId}`)
|
|
}
|
|
|
|
async createAssetTask(assetId: number, taskType: string): Promise<TaskStatusInfo> {
|
|
const response = await apiClient.post(`/assets/${assetId}/tasks?task_type=${taskType}`)
|
|
return response.data
|
|
}
|
|
|
|
async createShotTask(shotId: number, taskType: string): Promise<TaskStatusInfo> {
|
|
const response = await apiClient.post(`/shots/${shotId}/tasks?task_type=${taskType}`)
|
|
return response.data
|
|
}
|
|
|
|
// Bulk Actions
|
|
async bulkUpdateStatus(taskIds: number[], status: string): Promise<BulkActionResult> {
|
|
const response = await apiClient.put('/tasks/bulk/status', {
|
|
task_ids: taskIds,
|
|
status
|
|
})
|
|
return response.data
|
|
}
|
|
|
|
async bulkAssignTasks(taskIds: number[], assignedUserId: number): Promise<BulkActionResult> {
|
|
const response = await apiClient.put('/tasks/bulk/assign', {
|
|
task_ids: taskIds,
|
|
assigned_user_id: assignedUserId
|
|
})
|
|
return response.data
|
|
}
|
|
}
|
|
|
|
export const taskService = new TaskService() |