Files
LinkDesk/frontend/src/services/project.ts
T
indigo c09710f4e5 Add per-project Department management
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.
2026-07-22 04:03:33 +08:00

226 lines
6.9 KiB
TypeScript

import { apiClient } from './api'
export interface Project {
id: number
name: string
code_name: string
client_name: string
project_type: 'tv' | 'cinema' | 'game'
description?: string
status: 'planning' | 'in_progress' | 'on_hold' | 'completed' | 'cancelled'
start_date?: string
end_date?: string
created_at: string
updated_at: string
member_count?: number
episode_count?: number
asset_count?: number
department_role?: string
project_members?: ProjectMember[]
thumbnail_url?: string | null
custom_asset_task_types?: string[]
custom_shot_task_types?: string[]
}
export interface ProjectMember {
id: number
user_id: number
project_id: number
department_role?: string
joined_at: string
user_email: string
user_first_name: string
user_last_name: string
user_avatar_url?: string | null
}
export interface ProjectCreate {
name: string
code_name: string
client_name: string
project_type: 'tv' | 'cinema' | 'game'
description?: string | null
status?: 'planning' | 'in_progress' | 'on_hold' | 'completed' | 'cancelled'
start_date?: string | null
end_date?: string | null
}
export interface ProjectUpdate {
name?: string
code_name?: string
client_name?: string
project_type?: 'tv' | 'cinema' | 'game'
description?: string | null
status?: 'planning' | 'in_progress' | 'on_hold' | 'completed' | 'cancelled'
start_date?: string | null
end_date?: string | null
}
export interface ProjectMemberCreate {
user_id: number
department_role?: string
}
export interface ProjectMemberUpdate {
department_role?: string
}
export interface DeliveryMovieSpec {
resolution: string
format: string
codec?: string
quality?: string
}
export interface ProjectTechnicalSpecs {
frame_rate?: number
data_drive_path?: string
publish_storage_path?: string
delivery_image_resolution?: string
delivery_movie_specs_by_department?: Record<string, DeliveryMovieSpec>
}
export interface AssetTaskTemplates {
characters: string[]
props: string[]
sets: string[]
vehicles: string[]
}
export interface ProjectSettings {
upload_data_location?: string
asset_task_templates?: AssetTaskTemplates
shot_task_templates?: string[]
enabled_asset_tasks?: Record<string, string[]>
enabled_shot_tasks?: string[]
}
export interface SubmissionTypeConfig {
allowed_extensions: string[]
naming_pattern_is_regex: boolean
naming_pattern?: string | null
check_naming: boolean
required: boolean
check_movie_spec: boolean
movie_resolution?: string | null
movie_format?: string | null
movie_codec?: string | null
movie_frame_rate?: number | null
}
export interface ProjectSubmissionConfig {
config_by_task_type: Record<string, SubmissionTypeConfig>
}
export const projectService = {
async getUserProjects(): Promise<Project[]> {
const response = await apiClient.get('/projects/')
return response.data
},
async getProject(projectId: number, includeMembers: boolean = false): Promise<Project> {
const response = await apiClient.get(`/projects/${projectId}`, {
params: { include_members: includeMembers }
})
return response.data
},
async createProject(project: ProjectCreate): Promise<Project> {
// Clean up the project data - convert empty strings to null for optional fields
const cleanedProject = {
...project,
start_date: project.start_date || null,
end_date: project.end_date || null,
description: project.description || null
}
const response = await apiClient.post('/projects/', cleanedProject)
return response.data
},
async updateProject(projectId: number, project: ProjectUpdate): Promise<Project> {
// Clean up the project data - convert empty strings to null for optional fields
const cleanedProject = {
...project,
start_date: project.start_date || null,
end_date: project.end_date || null,
description: project.description || null
}
const response = await apiClient.put(`/projects/${projectId}`, cleanedProject)
return response.data
},
async deleteProject(projectId: number): Promise<void> {
await apiClient.delete(`/projects/${projectId}`)
},
async getProjectMembers(projectId: number): Promise<ProjectMember[]> {
const response = await apiClient.get(`/projects/${projectId}/members`)
return response.data
},
async addProjectMember(projectId: number, member: ProjectMemberCreate): Promise<ProjectMember> {
const response = await apiClient.post(`/projects/${projectId}/members`, member)
return response.data
},
async updateProjectMember(projectId: number, memberId: number, member: ProjectMemberUpdate): Promise<ProjectMember> {
const response = await apiClient.put(`/projects/${projectId}/members/${memberId}`, member)
return response.data
},
async removeProjectMember(projectId: number, memberId: number): Promise<void> {
await apiClient.delete(`/projects/${projectId}/members/${memberId}`)
},
async getProjectTechnicalSpecs(projectId: number): Promise<ProjectTechnicalSpecs> {
const response = await apiClient.get(`/projects/${projectId}/technical-specs`)
return response.data
},
async updateProjectTechnicalSpecs(projectId: number, specs: ProjectTechnicalSpecs): Promise<ProjectTechnicalSpecs> {
const response = await apiClient.put(`/projects/${projectId}/technical-specs`, specs)
return response.data
},
async setDefaultTechnicalSpecs(projectId: number): Promise<ProjectTechnicalSpecs> {
const response = await apiClient.post(`/projects/${projectId}/technical-specs/defaults`)
return response.data
},
async getProjectSettings(projectId: number): Promise<ProjectSettings> {
const response = await apiClient.get(`/projects/${projectId}/settings`)
return response.data
},
async updateProjectSettings(projectId: number, settings: ProjectSettings): Promise<ProjectSettings> {
const response = await apiClient.put(`/projects/${projectId}/settings`, settings)
return response.data
},
async getProjectSubmissionConfig(projectId: number): Promise<ProjectSubmissionConfig> {
const response = await apiClient.get(`/projects/${projectId}/submission-config`)
return response.data
},
async updateProjectSubmissionConfig(projectId: number, config: ProjectSubmissionConfig): Promise<ProjectSubmissionConfig> {
const response = await apiClient.put(`/projects/${projectId}/submission-config`, config)
return response.data
},
async uploadThumbnail(projectId: number, file: File): Promise<{ message: string; thumbnail_url: string }> {
const formData = new FormData()
formData.append('file', file)
const response = await apiClient.post(`/projects/${projectId}/thumbnail`, formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
return response.data
},
async deleteThumbnail(projectId: number): Promise<void> {
await apiClient.delete(`/projects/${projectId}/thumbnail`)
}
}