Add search functionality with SearchBar component, update YearDashboard to filter people and gifts based on search input, and enhance GiftModal and PersonModal for better state management.

This commit is contained in:
Norbert Maciaszek
2025-11-25 23:15:56 +01:00
parent 6b50100ba7
commit 52fd699050
5 changed files with 83 additions and 12 deletions

View File

@@ -7,13 +7,14 @@
let { personId, personName } = $props(); let { personId, personName } = $props();
let isOpen = $state(false); let isOpen = $state(false);
let gift: Omit<Gift, 'id' | 'personId'> = $state({ const initGift = {
name: '', name: '',
price: 0, price: 0,
url: '', url: '',
description: '', description: '',
status: 'PLANNED' status: 'PLANNED'
}); } as const;
let gift: Omit<Gift, 'id' | 'personId'> = $state(initGift);
const open = () => { const open = () => {
isOpen = true; isOpen = true;
@@ -34,6 +35,8 @@
status: gift.status status: gift.status
} }
}).then(close); }).then(close);
gift = initGift;
}; };
const inputClasses = [ const inputClasses = [

View File

@@ -15,8 +15,13 @@
isOpen = false; isOpen = false;
}; };
const handleAddYear = () => { const handleAddYear = (shouldClose = true) => {
addPerson({ year, name }).then(close); addPerson({ year, name }).then(() => {
if (shouldClose) {
close();
}
});
name = '';
}; };
</script> </script>
@@ -37,8 +42,9 @@
]} ]}
/> />
</fieldset> </fieldset>
<div class="flex justify-end mt-6"> <div class="flex justify-end mt-6 gap-2">
<button class="btn btn-error" onclick={handleAddYear}>Dodaj {name}</button> <button class="btn btn-error" onclick={() => handleAddYear(false)}>Następna osoba</button>
<button class="btn btn-error" onclick={() => handleAddYear(true)}>Dodaj {name}</button>
</div> </div>
</Modal> </Modal>
{/if} {/if}

View File

@@ -0,0 +1,20 @@
<script>
import { searchStore } from '$lib/store/search.svelte';
import { Search } from 'lucide-svelte';
import { slide } from 'svelte/transition';
let showSearch = $state(false);
</script>
<div class="flex items-center gap-2">
{#if showSearch}
<input
transition:slide={{ axis: 'x' }}
bind:value={searchStore.value}
type="text"
placeholder="Szukaj"
class="border-none rounded-xl bg-neutral-300"
/>
{/if}
<Search class="cursor-pointer" onclick={() => (showSearch = !showSearch)} />
</div>

View File

@@ -1,9 +1,11 @@
<script lang="ts"> <script lang="ts">
import { formatCurrency } from '$lib/helpers/formatCurrency'; import { formatCurrency } from '$lib/helpers/formatCurrency';
import { getPeople, getYear } from '$lib/remotes/db.remote'; import { getPeople, getYear } from '$lib/remotes/db.remote';
import { searchStore } from '$lib/store/search.svelte';
import { Calendar, GiftIcon, UserPlus } from 'lucide-svelte'; import { Calendar, GiftIcon, UserPlus } from 'lucide-svelte';
import PersonCard from '../molecules/PersonCard.svelte'; import PersonCard from '../molecules/PersonCard.svelte';
import PersonModal from '../molecules/PersonModal.svelte'; import PersonModal from '../molecules/PersonModal.svelte';
import SearchBar from '../molecules/SearchBar.svelte';
let { year } = $props(); let { year } = $props();
@@ -19,9 +21,37 @@
0 0
) )
); );
let totalBought = $derived(
people.reduce(
(sum, person) =>
sum +
person.gifts
.filter((gift) => gift.status === 'BOUGHT')
.reduce((sum, gift) => sum + (gift.price ?? 0), 0),
0
)
);
let totalBudget = $derived(data.budget); let totalBudget = $derived(data.budget);
let remainingBudget = $derived(totalBudget - totalSpent); let remainingBudget = $derived(totalBudget - totalBought);
let progress = $derived((totalSpent / totalBudget) * 100); let progress = $derived((totalBought / totalBudget) * 100);
let filteredPeople = $derived.by(() => {
const shouldFilterPerson =
searchStore.value.length > 0 && searchStore.value.includes('person:');
if (shouldFilterPerson) {
const personName = searchStore.value.split('person:')[1].toLocaleLowerCase();
return people.filter((person) => person.name.toLocaleLowerCase().includes(personName));
}
const shouldFilterGift = searchStore.value.length > 0 && searchStore.value.includes('gift:');
if (shouldFilterGift) {
const giftName = searchStore.value.split('gift:')[1].toLocaleLowerCase();
return people.filter((person) =>
person.gifts.some((gift) => gift.name.toLocaleLowerCase().includes(giftName))
);
}
return people;
});
</script> </script>
<section class="my-block relative"> <section class="my-block relative">
@@ -49,8 +79,14 @@
<div class="mt-6"> <div class="mt-6">
<div class="flex justify-between text-xs mb-2 text-green-100 font-medium"> <div class="flex justify-between text-xs mb-2 text-green-100 font-medium">
<span>Wydane: {formatCurrency(totalSpent)}</span> <span class="flex flex-col">
<span>Zostało: {formatCurrency(remainingBudget)}</span> <span>Wydane: {formatCurrency(totalBought)}</span>
<span>Planowane: {formatCurrency(totalSpent)}</span>
</span>
<span class="flex flex-col items-end">
<span>Pozostało: {formatCurrency(remainingBudget)}</span>
<span>Bufor: {formatCurrency(totalBudget - totalSpent)}</span>
</span>
</div> </div>
<progress value={progress} max={100} class="progress progress-neutral" /> <progress value={progress} max={100} class="progress progress-neutral" />
</div> </div>
@@ -63,10 +99,13 @@
<UserPlus class="w-5 h-5 mr-2 text-red-600" /> <UserPlus class="w-5 h-5 mr-2 text-red-600" />
Osoby Osoby
</h2> </h2>
<PersonModal {year} /> <div class="flex items-center gap-4">
<SearchBar />
<PersonModal {year} />
</div>
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
{#each people as person (person.id)} {#each filteredPeople as person (person.id)}
<PersonCard {person} /> <PersonCard {person} />
{:else} {:else}
<div class="text-center py-12 border-2 border-dashed border-gray-200 rounded-xl"> <div class="text-center py-12 border-2 border-dashed border-gray-200 rounded-xl">

View File

@@ -0,0 +1,3 @@
export const searchStore = $state({
value: ''
});