diff --git a/src/lib/components/inputs/TagFilter.svelte b/src/lib/components/inputs/TagFilter.svelte new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/index.ts b/src/lib/index.ts index 6d1ccc9..5278563 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -25,6 +25,9 @@ export const availableStudySpaceTags = [ export const volumeTags = ["Silent", "Quiet", "Some Noise", "Loud"]; export const wifiTags = ["Good WiFi", "Moderate WiFi", "Bad WiFi", "No WiFi"]; export const powerOutletTags = ["Many Outlets", "Some Outlets", "No Outlets"]; + +export const allTags = [...availableStudySpaceTags, ...volumeTags, ...wifiTags, ...powerOutletTags]; + export const reportTypes = [ "Inappropriate content", "Duplicate content", diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 6e717f7..19f132f 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -3,9 +3,51 @@ import defaultImg from "$lib/assets/study_space.png"; import crossUrl from "$lib/assets/cross.svg"; import Navbar from "$lib/components/Navbar.svelte"; + import { allTags } from "$lib"; const { data } = $props(); const { studySpaces, supabase } = $derived(data); + + let selectedTags = $state([]); + let tagFilter = $state(""); + let tagFilterElem = $state(); + let filteredTags = $derived( + allTags + .filter((tag) => tag.toLowerCase().includes(tagFilter.toLowerCase())) + .filter((tag) => allTags.includes(tag)) + ); + + let filteredStudySpaces = $derived( + selectedTags.length === 0 + ? studySpaces + : studySpaces.filter((space) => { + const allTags = [ + ...(space.tags || []), + space.volume, + space.wifi, + space.power + ].filter(Boolean); + + return selectedTags.every((tag) => allTags.includes(tag)); + }) + ); + + let dropdownVisible = $state(false); + + function deleteTag(tagName: string) { + return () => { + selectedTags = selectedTags.filter((tag) => tag !== tagName); + }; + } + + function addTag(tagName: string) { + return () => { + if (!selectedTags.includes(tagName)) { + selectedTags.push(tagName); + } + tagFilter = ""; + }; + } @@ -15,7 +57,58 @@
- {#each studySpaces as studySpace (studySpace.id)} +
+
+
+ {#each selectedTags as tagName (tagName)} + + {/each} + { + dropdownVisible = true; + }} + onblur={() => { + dropdownVisible = false; + }} + onkeypress={(event) => { + if (event.key === "Enter") { + const tag = filteredTags[0]; + if (tag) addTag(tag)(); + } + }} + placeholder="Search by tags..." + /> + {#if dropdownVisible} +
+ {#each filteredTags as avaliableTag (avaliableTag)} + + {/each} +
+ {/if} +
+
+
+ + {#each filteredStudySpaces as studySpace (studySpace.id)} {@const imgUrl = studySpace.study_space_images.length > 0 ? supabase.storage @@ -47,6 +140,101 @@ transform: rotate(45deg); } + .tag-filter-container { + grid-column: 1 / -1; + display: flex; + justify-content: center; + margin-bottom: 1rem; + } + + form { + display: flex; + flex-direction: column; + padding: 1.5rem; + gap: 0.5rem; + max-width: 32rem; + margin: 0 auto; + } + + .tagDisplay { + display: flex; + gap: 0.4rem; + flex-wrap: wrap; + align-items: left; + justify-content: left; + position: relative; + width: 100%; + height: auto; + padding: 0.5rem; + border-radius: 0.5rem; + border: 2px solid #eaffeb; + background: none; + color: #eaffeb; + font-size: 1rem; + } + .tagInput { + width: 100%; + height: 100%; + background: none; + color: #eaffeb; + font-size: 1rem; + border: none; + outline: none; + } + ::placeholder { + color: #859a90; + opacity: 1; + } + .tag { + display: flex; + align-items: center; + border-radius: 0.25rem; + background-color: #2e4653; + color: #eaffeb; + font-size: 0.9rem; + cursor: pointer; + border-width: 0rem; + } + .tag img { + width: 1rem; + height: 1rem; + margin-left: 0.2rem; + } + + .tagDropdown { + width: 100%; + display: flex; + gap: 0.4rem; + flex-wrap: wrap; + position: absolute; + background-color: #2e4653; + box-shadow: 1px 1px 0.5rem rgba(0, 0, 0, 0.5); + border-radius: 0.5rem; + overflow-y: auto; + max-height: 10rem; + top: 100%; + left: 50%; + transform: translateX(-50%); + } + + .avaliableTag { + width: 100%; + text-align: left; + background: none; + border: none; + color: #eaffeb; + font-size: 0.9rem; + margin: 0%; + padding: 0 0.8rem 0.4rem; + } + .avaliableTag:first-child { + padding-top: 0.6rem; + background-color: hsl(201, 26%, 60%); + } + .avaliableTag:last-child { + padding-bottom: 0.6rem; + } + @media (max-width: 20rem) { main { grid-template-columns: 1fr;