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
}
/**
* 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
*/
@ -123,15 +136,25 @@ export class JobAdapter extends BaseAdapter {
* Gets all accessories available for a specific job
* Only returns data if the job supports accessories
*/
async getAccessories(jobId: string): Promise<JobAccessory[]> {
const response = await this.request<{ accessories: JobAccessory[] }>(
`/jobs/${jobId}/accessories`,
{
method: 'GET',
cacheTTL: 300000 // Cache for 5 minutes
}
)
return response.accessories
async getAccessoriesForJob(jobId: string): Promise<JobAccessory[]> {
return this.request<JobAccessory[]>(`/jobs/${jobId}/accessories`, {
method: 'GET',
cacheTTL: 300000 // Cache for 5 minutes
})
}
/**
* 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}`)
}
// ============================================
// 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
*/
clearJobCache() {
this.clearCache('/jobs')
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
* @returns Query options for fetching job accessories
*/
accessories: (jobId: string) =>
accessoriesForJob: (jobId: string) =>
queryOptions({
queryKey: ['jobs', jobId, 'accessories'] as const,
queryFn: () => jobAdapter.getAccessories(jobId),
queryFn: () => jobAdapter.getAccessoriesForJob(jobId),
enabled: !!jobId,
staleTime: 1000 * 60 * 30, // 30 minutes
gcTime: 1000 * 60 * 60 // 1 hour
@ -165,6 +165,39 @@ export const jobQueries = {
queryFn: () => jobAdapter.getAllSkills(),
staleTime: 1000 * 60 * 30, // 30 minutes
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,
skillsSearch: (jobId: string, params?: Omit<SearchJobSkillsParams, 'jobId' | 'page'>) =>
[...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
}
/**
* 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 {
id: string
granblue_id: string
job: Job
name: {
[key: string]: string
en: string
ja: string
}
rarity: number
}
// Re-export from entities for backwards compatibility
export type { JobAccessory } from './api/entities'

View file

@ -201,12 +201,15 @@ export interface JobSkill {
actionId?: number // Unique game ID
}
// JobAccessory entity
// JobAccessory entity from JobAccessoryBlueprint
export interface JobAccessory {
id: string
name: LocalizedName
slug: 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

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
}
}
/**
* 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