feat(ui): add skeleton loaders and category tabs

Add Skeleton component for improved loading states in ItemsList
Extract Tabs component and support recipe category filtering
This commit is contained in:
2026-04-30 14:09:11 +08:00
parent b39e37ca28
commit ba5aae7136
7 changed files with 278 additions and 25 deletions

View File

@@ -961,7 +961,17 @@ export async function deleteItem(id: number, userId: number) {
});
}
export async function listRecipes() {
export async function listRecipes(paramsQuery: QueryParams = {}) {
const params: unknown[] = [];
const conditions: string[] = [];
const categoryId = Number(asString(paramsQuery.categoryId));
if (Number.isInteger(categoryId) && categoryId > 0) {
params.push(categoryId);
conditions.push(`result_item.category_id = $${params.length}`);
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
return query(`
SELECT
r.id,
@@ -976,8 +986,9 @@ export async function listRecipes() {
FROM recipes r
JOIN items result_item ON result_item.id = r.item_id
${auditJoins('r', 'recipe_created_user', 'recipe_updated_user')}
${whereClause}
ORDER BY result_item.name
`);
`, params);
}
export async function getRecipe(id: number) {

View File

@@ -248,7 +248,7 @@ app.delete('/api/items/:id', async (request, reply) => {
return deleted ? reply.code(204).send() : reply.code(404).send({ message: 'Not found' });
});
app.get('/api/recipes', async () => listRecipes());
app.get('/api/recipes', async (request) => listRecipes(request.query as Record<string, string | string[] | undefined>));
app.get('/api/recipes/:id', async (request, reply) => {
const { id } = request.params as { id: string };