feat(admin): implement management UI and CRUD APIs for all entities

Add full CRUD operations for Pokemon, Habitats, Items, Recipes, and Configs
Switch package manager from npm to pnpm across the project
Remove static seed data in favor of UI-driven data management
This commit is contained in:
2026-04-29 18:01:46 +08:00
parent b428595769
commit f6a40097c1
12 changed files with 1514 additions and 160 deletions

View File

@@ -79,7 +79,57 @@ export interface Options {
favoriteThings: NamedEntity[];
itemCategories: NamedEntity[];
itemUsages: NamedEntity[];
acquisitionMethods: NamedEntity[];
itemTags: NamedEntity[];
maps: NamedEntity[];
}
export type ConfigType =
| 'skills'
| 'environments'
| 'favorite-things'
| 'item-categories'
| 'item-usages'
| 'acquisition-methods'
| 'item-tags'
| 'maps';
export interface PokemonPayload {
id: number;
name: string;
environmentId: number;
skillIds: number[];
favoriteThingIds: number[];
}
export interface ItemPayload {
name: string;
categoryId: number;
usageId: number;
recipeId: number | null;
dyeable: boolean;
dualDyeable: boolean;
patternEditable: boolean;
acquisitionMethodIds: number[];
tagIds: number[];
}
export interface RecipePayload {
name: string;
acquisitionMethodIds: number[];
materials: Array<{ itemId: number; quantity: number }>;
}
export interface HabitatPayload {
name: string;
recipeItems: Array<{ itemId: number; quantity: number }>;
pokemonAppearances: Array<{
pokemonId: number;
mapId: number;
timeOfDay: string;
weather: string;
rarity: number;
}>;
}
export function buildQuery(params: Record<string, string | number | undefined>): string {
@@ -105,16 +155,63 @@ async function getJson<T>(path: string): Promise<T> {
return response.json() as Promise<T>;
}
async function sendJson<T>(path: string, method: 'POST' | 'PUT', body: unknown): Promise<T> {
const response = await fetch(`${apiBaseUrl}${path}`, {
method,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
if (!response.ok) {
throw new Error(`Request failed with ${response.status}`);
}
return response.json() as Promise<T>;
}
async function deleteJson(path: string): Promise<void> {
const response = await fetch(`${apiBaseUrl}${path}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(`Request failed with ${response.status}`);
}
}
export const api = {
options: () => getJson<Options>('/api/options'),
config: (type: ConfigType) => getJson<Array<Skill | NamedEntity>>(`/api/admin/config/${type}`),
createConfig: (type: ConfigType, payload: { name: string; subcategory?: string | null }) =>
sendJson<Skill | NamedEntity>(`/api/admin/config/${type}`, 'POST', payload),
updateConfig: (type: ConfigType, id: number, payload: { name: string; subcategory?: string | null }) =>
sendJson<Skill | NamedEntity>(`/api/admin/config/${type}/${id}`, 'PUT', payload),
deleteConfig: (type: ConfigType, id: number) => deleteJson(`/api/admin/config/${type}/${id}`),
pokemon: (params: Record<string, string | number | undefined>) =>
getJson<Pokemon[]>(`/api/pokemon${buildQuery(params)}`),
pokemonDetail: (id: string | number) => getJson<PokemonDetail>(`/api/pokemon/${id}`),
createPokemon: (payload: PokemonPayload) => sendJson<PokemonDetail>('/api/pokemon', 'POST', payload),
updatePokemon: (id: string | number, payload: PokemonPayload) =>
sendJson<PokemonDetail>(`/api/pokemon/${id}`, 'PUT', payload),
deletePokemon: (id: string | number) => deleteJson(`/api/pokemon/${id}`),
habitats: () => getJson<Habitat[]>('/api/habitats'),
habitatDetail: (id: string | number) => getJson<HabitatDetail>(`/api/habitats/${id}`),
createHabitat: (payload: HabitatPayload) => sendJson<HabitatDetail>('/api/habitats', 'POST', payload),
updateHabitat: (id: string | number, payload: HabitatPayload) =>
sendJson<HabitatDetail>(`/api/habitats/${id}`, 'PUT', payload),
deleteHabitat: (id: string | number) => deleteJson(`/api/habitats/${id}`),
items: (params: Record<string, string | number | undefined>) =>
getJson<Item[]>(`/api/items${buildQuery(params)}`),
itemDetail: (id: string | number) => getJson<ItemDetail>(`/api/items/${id}`),
createItem: (payload: ItemPayload) => sendJson<ItemDetail>('/api/items', 'POST', payload),
updateItem: (id: string | number, payload: ItemPayload) => sendJson<ItemDetail>(`/api/items/${id}`, 'PUT', payload),
deleteItem: (id: string | number) => deleteJson(`/api/items/${id}`),
recipes: () => getJson<Recipe[]>('/api/recipes'),
recipeDetail: (id: string | number) => getJson<RecipeDetail>(`/api/recipes/${id}`)
recipeDetail: (id: string | number) => getJson<RecipeDetail>(`/api/recipes/${id}`),
createRecipe: (payload: RecipePayload) => sendJson<RecipeDetail>('/api/recipes', 'POST', payload),
updateRecipe: (id: string | number, payload: RecipePayload) =>
sendJson<RecipeDetail>(`/api/recipes/${id}`, 'PUT', payload),
deleteRecipe: (id: string | number) => deleteJson(`/api/recipes/${id}`)
};