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:
@@ -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 = [
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
20
src/lib/components/molecules/SearchBar.svelte
Normal file
20
src/lib/components/molecules/SearchBar.svelte
Normal 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>
|
||||||
@@ -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>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<SearchBar />
|
||||||
<PersonModal {year} />
|
<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">
|
||||||
|
|||||||
3
src/lib/store/search.svelte.ts
Normal file
3
src/lib/store/search.svelte.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const searchStore = $state({
|
||||||
|
value: ''
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user