diff --git a/src/lib/api/adapters/job.adapter.ts b/src/lib/api/adapters/job.adapter.ts index a9407a7d..90f025d8 100644 --- a/src/lib/api/adapters/job.adapter.ts +++ b/src/lib/api/adapters/job.adapter.ts @@ -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 { - 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 { + return this.request(`/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 { + const response = await this.request('/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 { + const params = accessoryType ? `?accessory_type=${accessoryType}` : '' + return this.request(`/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 { + return this.request(`/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 { + const response = await this.request('/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): Promise { + const response = await this.request(`/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 { + 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') } } diff --git a/src/lib/api/queries/job.queries.ts b/src/lib/api/queries/job.queries.ts index 16e988eb..5a826e5b 100644 --- a/src/lib/api/queries/job.queries.ts +++ b/src/lib/api/queries/job.queries.ts @@ -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) => [...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 +} diff --git a/src/lib/types/JobAccessory.d.ts b/src/lib/types/JobAccessory.d.ts index 09a462fc..dffd504f 100644 --- a/src/lib/types/JobAccessory.d.ts +++ b/src/lib/types/JobAccessory.d.ts @@ -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' diff --git a/src/lib/types/api/entities.ts b/src/lib/types/api/entities.ts index 31047a8e..4b653403 100644 --- a/src/lib/types/api/entities.ts +++ b/src/lib/types/api/entities.ts @@ -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 diff --git a/src/lib/utils/jobAccessoryUtils.ts b/src/lib/utils/jobAccessoryUtils.ts new file mode 100644 index 00000000..9a89b576 --- /dev/null +++ b/src/lib/utils/jobAccessoryUtils.ts @@ -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` +} diff --git a/src/lib/utils/jobUtils.ts b/src/lib/utils/jobUtils.ts index f8353d6d..95896364 100644 --- a/src/lib/utils/jobUtils.ts +++ b/src/lib/utils/jobUtils.ts @@ -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