add job accessory types and adapter methods

- add accessoryType field to JobAccessory entity
- add accessory CRUD methods to job adapter
- add accessory query options
- add jobAccessoryUtils with type helpers
This commit is contained in:
Justin Edmund 2026-01-04 02:53:04 -08:00
parent 4401191738
commit 29211709ef
6 changed files with 232 additions and 26 deletions

View file

@ -42,6 +42,19 @@ export interface JobSkillPayload {
action_id?: number action_id?: number
} }
/**
* Payload for creating/updating a job accessory
*/
export interface JobAccessoryPayload {
name_en: string
name_jp?: string
granblue_id: string
rarity?: number
release_date?: string
accessory_type: number // 1 = Shield, 2 = Manatura
job_id?: string
}
/** /**
* Payload for updating a job entity * Payload for updating a job entity
*/ */
@ -123,15 +136,25 @@ export class JobAdapter extends BaseAdapter {
* Gets all accessories available for a specific job * Gets all accessories available for a specific job
* Only returns data if the job supports accessories * Only returns data if the job supports accessories
*/ */
async getAccessories(jobId: string): Promise<JobAccessory[]> { async getAccessoriesForJob(jobId: string): Promise<JobAccessory[]> {
const response = await this.request<{ accessories: JobAccessory[] }>( return this.request<JobAccessory[]>(`/jobs/${jobId}/accessories`, {
`/jobs/${jobId}/accessories`, method: 'GET',
{ cacheTTL: 300000 // Cache for 5 minutes
method: 'GET', })
cacheTTL: 300000 // Cache for 5 minutes }
}
) /**
return response.accessories * Creates a new job entity (database admin function)
* @param data The job data
*/
async createJob(data: JobUpdatePayload): Promise<Job> {
const response = await this.request<Job>('/jobs', {
method: 'POST',
body: data
})
// Clear jobs cache to reflect the new job
this.clearCache('/jobs')
return response
} }
/** /**
@ -327,12 +350,78 @@ export class JobAdapter extends BaseAdapter {
this.clearCache(`/parties/${partyId}`) this.clearCache(`/parties/${partyId}`)
} }
// ============================================
// Job Accessory Methods (Database Management)
// ============================================
/**
* Gets all job accessories
* @param accessoryType Optional filter by type (1=Shield, 2=Manatura)
*/
async getAllAccessories(accessoryType?: number): Promise<JobAccessory[]> {
const params = accessoryType ? `?accessory_type=${accessoryType}` : ''
return this.request<JobAccessory[]>(`/job_accessories${params}`, {
method: 'GET',
cacheTTL: 300000 // Cache for 5 minutes
})
}
/**
* Gets a single job accessory by ID or granblue_id
*/
async getAccessoryById(id: string): Promise<JobAccessory> {
return this.request<JobAccessory>(`/job_accessories/${id}`, {
method: 'GET',
cacheTTL: 300000 // Cache for 5 minutes
})
}
/**
* Creates a new job accessory
* @param data The accessory data
*/
async createAccessory(data: JobAccessoryPayload): Promise<JobAccessory> {
const response = await this.request<JobAccessory>('/job_accessories', {
method: 'POST',
body: data
})
this.clearCache('/job_accessories')
return response
}
/**
* Updates an existing job accessory
* @param id The accessory's ID or granblue_id
* @param data The updated accessory data
*/
async updateAccessory(id: string, data: Partial<JobAccessoryPayload>): Promise<JobAccessory> {
const response = await this.request<JobAccessory>(`/job_accessories/${id}`, {
method: 'PUT',
body: data
})
this.clearCache('/job_accessories')
this.clearCache(`/job_accessories/${id}`)
return response
}
/**
* Deletes a job accessory
* @param id The accessory's ID or granblue_id
*/
async deleteAccessory(id: string): Promise<void> {
await this.request(`/job_accessories/${id}`, {
method: 'DELETE'
})
this.clearCache('/job_accessories')
}
/** /**
* Clears the cache for job-related data * Clears the cache for job-related data
*/ */
clearJobCache() { clearJobCache() {
this.clearCache('/jobs') this.clearCache('/jobs')
this.clearCache('/search/job_skills') this.clearCache('/search/job_skills')
this.clearCache('/job_accessories')
} }
} }

View file

@ -140,15 +140,15 @@ export const jobQueries = {
}), }),
/** /**
* Job accessories query options * Job accessories query options (for a specific job)
* *
* @param jobId - Job ID to fetch accessories for * @param jobId - Job ID to fetch accessories for
* @returns Query options for fetching job accessories * @returns Query options for fetching job accessories
*/ */
accessories: (jobId: string) => accessoriesForJob: (jobId: string) =>
queryOptions({ queryOptions({
queryKey: ['jobs', jobId, 'accessories'] as const, queryKey: ['jobs', jobId, 'accessories'] as const,
queryFn: () => jobAdapter.getAccessories(jobId), queryFn: () => jobAdapter.getAccessoriesForJob(jobId),
enabled: !!jobId, enabled: !!jobId,
staleTime: 1000 * 60 * 30, // 30 minutes staleTime: 1000 * 60 * 30, // 30 minutes
gcTime: 1000 * 60 * 60 // 1 hour gcTime: 1000 * 60 * 60 // 1 hour
@ -165,6 +165,39 @@ export const jobQueries = {
queryFn: () => jobAdapter.getAllSkills(), queryFn: () => jobAdapter.getAllSkills(),
staleTime: 1000 * 60 * 30, // 30 minutes staleTime: 1000 * 60 * 30, // 30 minutes
gcTime: 1000 * 60 * 60 // 1 hour gcTime: 1000 * 60 * 60 // 1 hour
}),
// ============================================
// Job Accessory Database Queries
// ============================================
/**
* All job accessories list query options
*
* @param accessoryType - Optional filter by type (1=Shield, 2=Manatura)
* @returns Query options for fetching all job accessories
*/
accessoriesList: (accessoryType?: number) =>
queryOptions({
queryKey: ['jobAccessories', { accessoryType }] as const,
queryFn: () => jobAdapter.getAllAccessories(accessoryType),
staleTime: 1000 * 60 * 30, // 30 minutes
gcTime: 1000 * 60 * 60 // 1 hour
}),
/**
* Single job accessory query options
*
* @param id - Accessory ID or granblue_id
* @returns Query options for fetching a single accessory
*/
accessoryById: (id: string) =>
queryOptions({
queryKey: ['jobAccessories', id] as const,
queryFn: () => jobAdapter.getAccessoryById(id),
enabled: !!id,
staleTime: 1000 * 60 * 30, // 30 minutes
gcTime: 1000 * 60 * 60 // 1 hour
}) })
} }
@ -193,6 +226,15 @@ export const jobKeys = {
empSkills: (jobId: string) => [...jobKeys.all, jobId, 'emp_skills'] as const, empSkills: (jobId: string) => [...jobKeys.all, jobId, 'emp_skills'] as const,
skillsSearch: (jobId: string, params?: Omit<SearchJobSkillsParams, 'jobId' | 'page'>) => skillsSearch: (jobId: string, params?: Omit<SearchJobSkillsParams, 'jobId' | 'page'>) =>
[...jobKeys.skills(jobId), 'search', params] as const, [...jobKeys.skills(jobId), 'search', params] as const,
accessories: (jobId: string) => [...jobKeys.all, jobId, 'accessories'] as const, accessoriesForJob: (jobId: string) => [...jobKeys.all, jobId, 'accessories'] as const,
allSkills: () => [...jobKeys.all, 'skills', 'all'] as const allSkills: () => [...jobKeys.all, 'skills', 'all'] as const
} }
/**
* Job accessory query key helpers for cache invalidation
*/
export const jobAccessoryKeys = {
all: ['jobAccessories'] as const,
lists: (accessoryType?: number) => [...jobAccessoryKeys.all, { accessoryType }] as const,
detail: (id: string) => [...jobAccessoryKeys.all, id] as const
}

View file

@ -1,11 +1,2 @@
export interface JobAccessory { // Re-export from entities for backwards compatibility
id: string export type { JobAccessory } from './api/entities'
granblue_id: string
job: Job
name: {
[key: string]: string
en: string
ja: string
}
rarity: number
}

View file

@ -201,12 +201,15 @@ export interface JobSkill {
actionId?: number // Unique game ID actionId?: number // Unique game ID
} }
// JobAccessory entity // JobAccessory entity from JobAccessoryBlueprint
export interface JobAccessory { export interface JobAccessory {
id: string id: string
name: LocalizedName name: LocalizedName
slug: string
granblueId: string granblueId: string
rarity: number
releaseDate?: string
accessoryType: number // 1 = Shield, 2 = Manatura
job?: Job // Associated job (optional, included when available)
} }
// Raid entity from RaidBlueprint // Raid entity from RaidBlueprint

View file

@ -0,0 +1,50 @@
/**
* Job Accessory Utilities
*
* Helper functions for working with job accessories (Shields and Manatura).
*/
/**
* Accessory type constants
*/
export const ACCESSORY_TYPES = {
SHIELD: 1,
MANATURA: 2
} as const
export type AccessoryType = (typeof ACCESSORY_TYPES)[keyof typeof ACCESSORY_TYPES]
/**
* Gets the display name for an accessory type
*/
export function getAccessoryTypeName(type: number): string {
switch (type) {
case ACCESSORY_TYPES.SHIELD:
return 'Shield'
case ACCESSORY_TYPES.MANATURA:
return 'Manatura'
default:
return 'Unknown'
}
}
/**
* Gets options for accessory type filter/select
*/
export function getAccessoryTypeOptions(): Array<{ value: number; label: string }> {
return [
{ value: ACCESSORY_TYPES.SHIELD, label: 'Shield' },
{ value: ACCESSORY_TYPES.MANATURA, label: 'Manatura' }
]
}
/**
* Gets the image URL for a job accessory
* @param granblueId The accessory's granblue_id
* @param accessoryType The type of accessory (1=Shield, 2=Manatura)
*/
export function getJobAccessoryImageUrl(granblueId: string, accessoryType: number): string {
// Different asset paths based on accessory type
const folder = accessoryType === ACCESSORY_TYPES.SHIELD ? 'shield' : 'manatura'
return `/images/job-accessories/${folder}/${granblueId}.png`
}

View file

@ -290,3 +290,34 @@ export function validateSkillConfiguration(
errors errors
} }
} }
/**
* Proficiency options for job forms
*/
export const PROFICIENCIES = [
{ value: 0, label: 'None' },
{ value: 1, label: 'Sabre' },
{ value: 2, label: 'Dagger' },
{ value: 3, label: 'Axe' },
{ value: 4, label: 'Spear' },
{ value: 5, label: 'Bow' },
{ value: 6, label: 'Staff' },
{ value: 7, label: 'Melee' },
{ value: 8, label: 'Harp' },
{ value: 9, label: 'Gun' },
{ value: 10, label: 'Katana' }
] as const
/**
* Row options for job forms
*/
export const ROWS = [
{ value: '1', label: 'Class I' },
{ value: '2', label: 'Class II' },
{ value: '3', label: 'Class III' },
{ value: '4', label: 'Class IV' },
{ value: '5', label: 'Class V' },
{ value: 'ex', label: 'EX' },
{ value: 'ex2', label: 'EX II' },
{ value: 'o1', label: 'Origin I' }
] as const