Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add search function for milestone components #87

Merged
merged 12 commits into from
Sep 25, 2024
1 change: 1 addition & 0 deletions frontend/src/lib/components/ChildrenRegistration.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
});

console.log(childData);
await children.save();
await goto(nextpage as string);
} else {
showAlert = true;
Expand Down
46 changes: 45 additions & 1 deletion frontend/src/lib/components/Childrenpage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,50 @@
let data: ChildData[] = [];
let loading = true;

function searchName(data: any[], key: string): any[] {
if (key === '') {
return data;
} else {
const res = data.filter((item) => {
return item.header.toLowerCase().includes(key.toLowerCase());
});
return res;
}
}

function searchRemarks(data: any[], key: string): any[] {
if (key === '') {
return data;
} else {
const res = data.filter((item) => {
return item.summary.toLowerCase().includes(key.toLowerCase());
});
return res;
}
}

function searchAll(data: any[], key: string) {
return [...new Set([...searchName(data, key), ...searchRemarks(data, key)])];
}

const searchData = [
{
label: 'Alle',
placeholder: 'Alle Kategorien durchsuchen',
filterFunction: searchAll
},
{
label: 'Name',
placeholder: 'Kinder nach Namen durchsuchen',
filterFunction: searchName
},
{
label: 'Bemerkung',
placeholder: 'Bemerkungen zu Kindern durchsuchen',
filterFunction: searchRemarks
}
];

// this fetches dummy child data for the dummy user whenever the component is mounted into the dom
// it is conceptualized as emulating an API call that would normally fetch this from the server.
onMount(init);
Expand All @@ -104,8 +148,8 @@
<GalleryDisplay
{data}
itemComponent={CardDisplay}
searchableCol={'header'}
componentProps={createStyle(data)}
{searchData}
/>
{/if}
</div>
82 changes: 68 additions & 14 deletions frontend/src/lib/components/DataDisplay/GalleryDisplay.svelte
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
<script lang="ts">
import { Gallery, Heading, Search } from 'flowbite-svelte';

export let filterData = (data, col, searchTerm) => {
if (searchTerm === '') {
return data;
} else {
return data.filter((item) => item[col].toLowerCase().includes(searchTerm.toLowerCase()));
}
};
import { Button, Dropdown, DropdownItem, Gallery, Heading, Search } from 'flowbite-svelte';
import { ChevronDownOutline } from 'flowbite-svelte-icons';
import { tick } from 'svelte';

export let data;
export let header: string | null = null;
export let itemComponent;
export let withSearch = true;
export let searchableCol = '';
export let componentProps;
export let searchPlaceHolder = 'Durchsuchen';

export let searchData = [
{
label: 'Alle',
placeholder: 'Durchsuchen',
filterFunction: (data: any[], searchTerm: string): any[] => {
if (searchTerm === '') {
return data;
} else {
return data.filter((item) =>
Object.values(item).some((element) => {
return element.toLowerCase().includes(searchTerm.toLowerCase());
})
);
}
}
}
];

let searchCategory: string = searchData[0].label;
let searchPlaceHolder: string = searchData[0].placeholder;
let filterData = searchData[0].filterFunction;
let dropdownOpen: boolean = false;

// dynamic statements
let searchTerm = '';
$: filteredItems = withSearch === true ? filterData(data, searchableCol, searchTerm) : data;
$: filteredItems = withSearch === true ? filterData(data, searchTerm) : data;

// Create a new array of componentProps that matches the filtered data
$: filteredComponentProps = filteredItems.map((item) => {
Expand All @@ -41,8 +56,47 @@
{/if}

{#if withSearch}
<form class="m-2 w-full p-4">
<Search size="md" placeholder={searchPlaceHolder} bind:value={searchTerm} />
<form class="m-2 flex w-full rounded p-4">
{#if searchData.length > 1}
<!-- after example: https://flowbite-svelte.com/docs/forms/search-input#Search_with_dropdown -->
<div class="relative">
<Button
class="h-full whitespace-nowrap rounded-e-none border border-e-0 border-primary-700"
>
{searchCategory}
<ChevronDownOutline class="ms-2.5 h-2.5 w-2.5" />
</Button>
<Dropdown classContainer="flex w-auto" bind:open={dropdownOpen}>
{#each searchData as { label, placeholder, filterFunction }}
<DropdownItem
on:click={async () => {
searchCategory = label;
searchPlaceHolder = placeholder;
filterData = filterFunction;
dropdownOpen = false;
await tick();
}}
class={searchCategory === label ? 'underline' : ''}
>
{label}
</DropdownItem>
{/each}
</Dropdown>
</div>
<Search
class="rounded-e rounded-s-none py-2.5"
size="md"
placeholder={searchPlaceHolder}
bind:value={searchTerm}
/>
{:else}
<Search
size="md"
class="rounded py-2.5"
placeholder={searchPlaceHolder}
bind:value={searchTerm}
/>
{/if}
</form>
{/if}

Expand Down
23 changes: 3 additions & 20 deletions frontend/src/lib/components/MilestoneGroup.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,10 @@
import Breadcrumbs from '$lib/components/Breadcrumbs.svelte';
import CardDisplay from '$lib/components/DataDisplay/CardDisplay.svelte';
import GalleryDisplay from '$lib/components/DataDisplay/GalleryDisplay.svelte';

export let breadcrumbdata: any[] = [];
export let milestonedata: any[] = [];

function filterData(data: object[], dummy: any, key: string): object[] {
if (key === '') {
return data;
} else {
return data.filter((item) => {
// button label contains info about completion status => use for search
if (key === completeKey) {
return item.progress === 1;
} else if (key === incompleteKey) {
return item.progress < 1;
} else {
return item.header.toLowerCase().includes(key.toLowerCase());
}
});
}
}
export let searchData: any[] = [];

// FIXME:styling has no business being here... not sure where to put it though given thatparts of it are data dependent
export function createStyle(data) {
Expand Down Expand Up @@ -52,11 +37,9 @@
<GalleryDisplay
data={milestonedata.sort((a, b) => a.progress - b.progress)}
itemComponent={CardDisplay}
searchableCol={'header'}
componentProps={createStyle(milestonedata)}
searchPlaceHolder={`Nach Status (${completeKey}/${incompleteKey}) oder Titel durchsuchen`}
withSearch={true}
{filterData}
{searchData}
/>
</div>
</div>
23 changes: 4 additions & 19 deletions frontend/src/lib/components/MilestoneOverview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,6 @@
import GalleryDisplay from '$lib/components/DataDisplay/GalleryDisplay.svelte';
import { CheckCircleSolid, ExclamationCircleSolid } from 'flowbite-svelte-icons';

function filterData(data: object[], dummy: any, key: string): object[] {
if (key === '') {
return data;
} else {
return data.filter((item) => {
// button label contains info about completion status => use for search
if (key === completeKey) {
return item.complete === true;
} else if (key === incompleteKey) {
return item.complete === false;
} else {
return item.header.toLowerCase().includes(key.toLowerCase());
}
});
}
}

// FIXME: this must go eventually. Either must happen in the backend or there
// should be in a refactored version of the card component
function convertData(data: object[]): object[] {
Expand All @@ -30,6 +13,8 @@
header: item.title,
href: `${base}/milestone`, // hardcoded link for the moment
complete: item.answer !== null,
summary: item.desc,
answer: item.answer,
auxilliary: item.answer !== null ? CheckCircleSolid : ExclamationCircleSolid
};
});
Expand All @@ -38,6 +23,7 @@
const completeKey = 'fertig';
const incompleteKey = 'unfertig';
export let breadcrumbdata: object[] = [];
export let searchData: any[];
export let data: object[] = [];
const rawdata = convertData(data).sort((a, b) => a.complete - b.complete); // FIXME: the convert step should not be here and will be handeled backend-side
</script>
Expand All @@ -60,9 +46,8 @@
}
};
})}
searchPlaceHolder={`Nach Status (${completeKey}/${incompleteKey}) oder Titel durchsuchen`}
withSearch={true}
{filterData}
{searchData}
/>
</div>
</div>
Loading