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.
226 lines
6.9 KiB
TypeScript
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`)
|
|
}
|
|
} |