feat(planner): add isLoading prop to SwapSuggestionList — disables Pick buttons during PATCH

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 10:32:59 +02:00
parent 9482ecbf36
commit b4fa3ca23e
3 changed files with 21 additions and 2 deletions

View File

@@ -11,6 +11,7 @@
replacingMeta, replacingMeta,
recipes, recipes,
currentWeekRecipeIds, currentWeekRecipeIds,
isLoading = false,
onpick, onpick,
oncancel oncancel
}: { }: {
@@ -18,6 +19,7 @@
replacingMeta?: string; replacingMeta?: string;
recipes: Recipe[]; recipes: Recipe[];
currentWeekRecipeIds: Set<string>; currentWeekRecipeIds: Set<string>;
isLoading?: boolean;
onpick: (recipeId: string, recipeName: string) => void; onpick: (recipeId: string, recipeName: string) => void;
oncancel?: () => void; oncancel?: () => void;
} = $props(); } = $props();
@@ -97,7 +99,8 @@
<button <button
type="button" type="button"
onclick={() => onpick(recipe.id, recipe.name)} onclick={() => onpick(recipe.id, recipe.name)}
style="background: none; border: none; cursor: pointer; font-size: 11px; font-weight: 500; color: var(--green); font-family: var(--font-sans); flex-shrink: 0;" disabled={isLoading}
style="background: none; border: none; cursor: {isLoading ? 'default' : 'pointer'}; font-size: 11px; font-weight: 500; color: var(--green); font-family: var(--font-sans); flex-shrink: 0; opacity: {isLoading ? '0.4' : '1'};"
> >
Wählen Wählen
</button> </button>

View File

@@ -74,6 +74,18 @@ describe('SwapSuggestionList', () => {
expect(screen.getByTestId('swap-empty-state')).toBeTruthy(); expect(screen.getByTestId('swap-empty-state')).toBeTruthy();
}); });
it('disables all Wählen buttons when isLoading is true', () => {
render(SwapSuggestionList, { props: { ...baseProps, isLoading: true } });
const buttons = screen.getAllByRole('button', { name: /Wählen/i });
buttons.forEach((btn) => expect((btn as HTMLButtonElement).disabled).toBe(true));
});
it('Wählen buttons are enabled when isLoading is false', () => {
render(SwapSuggestionList, { props: { ...baseProps, isLoading: false } });
const buttons = screen.getAllByRole('button', { name: /Wählen/i });
buttons.forEach((btn) => expect((btn as HTMLButtonElement).disabled).toBe(false));
});
it('renders optional Abbrechen button when oncancel provided', () => { it('renders optional Abbrechen button when oncancel provided', () => {
render(SwapSuggestionList, { props: { ...baseProps, oncancel: vi.fn() } }); render(SwapSuggestionList, { props: { ...baseProps, oncancel: vi.fn() } });
expect(screen.getByRole('button', { name: /Abbrechen/i })).toBeTruthy(); expect(screen.getByRole('button', { name: /Abbrechen/i })).toBeTruthy();

View File

@@ -61,6 +61,7 @@
let pickerOpen = $state(false); let pickerOpen = $state(false);
let actionSheetOpen = $state(false); let actionSheetOpen = $state(false);
let swapSheetOpen = $state(false); let swapSheetOpen = $state(false);
let swapLoading = $state(false);
// Recipes already in any slot this week — used for ⚠ overlap warnings // Recipes already in any slot this week — used for ⚠ overlap warnings
let currentWeekRecipeIds = $derived( let currentWeekRecipeIds = $derived(
@@ -139,8 +140,10 @@
} }
async function handleSwapPick(recipeId: string, recipeName: string) { async function handleSwapPick(recipeId: string, recipeName: string) {
swapSheetOpen = false; swapLoading = true;
await handleRecipePick(recipeId, recipeName); await handleRecipePick(recipeId, recipeName);
swapSheetOpen = false;
swapLoading = false;
} }
function closePanelToIdle() { function closePanelToIdle() {
@@ -306,6 +309,7 @@
replacingMeta={replacingMeta || undefined} replacingMeta={replacingMeta || undefined}
recipes={sortedRecipes} recipes={sortedRecipes}
{currentWeekRecipeIds} {currentWeekRecipeIds}
isLoading={swapLoading}
onpick={handleSwapPick} onpick={handleSwapPick}
oncancel={() => (swapSheetOpen = false)} oncancel={() => (swapSheetOpen = false)}
/> />