Merge branch 'feat/upload-tags' into 'master'

feat: added tags on upload and view tweaked navbar

See merge request gk1623/drp-48!6

Co-authored-by: Gleb Koval <gleb@koval.net>
Co-authored-by: Barf-Vader <47476490+Barf-Vader@users.noreply.github.com>
This commit is contained in:
Temesgen, Tadios
2025-06-06 03:16:06 +00:00
9 changed files with 241 additions and 17 deletions

View File

@@ -21,11 +21,11 @@
display: flex;
position: sticky;
width: 100%;
height: 4rem;
height: 3.5rem;
top: 0;
left: 0;
right: 0;
background: linear-gradient(-77deg, #2e4653, #3a5b56);
background: linear-gradient(-77deg, #2e4653, #223a37);
box-shadow: 0rem 0rem 0.5rem #182125;
align-items: center;
overflow: hidden;

View File

@@ -15,7 +15,11 @@
<img src={imgSrc} {alt} />
<div class="description">
<h1>{space.location}</h1>
<p>{space.description}</p>
<div class="tagContainer">
{#each space.tags as tag (tag)}
<span class="tag">{tag}</span>
{/each}
</div>
</div>
</a>
@@ -33,7 +37,6 @@
.description {
padding: 0.5rem;
color: #edebe9;
font-size: 0.875rem;
}
img {
width: 100%;
@@ -41,4 +44,27 @@
aspect-ratio: 1 / 1;
object-fit: cover;
}
h1 {
margin-bottom: 0.5rem;
}
.tagContainer {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
border-radius: 0.5rem;
background: none;
}
.tag {
display: flex;
align-items: center;
border-radius: 0.25rem;
background-color: #2e4653;
color: #eaffeb;
font-size: 0.875rem;
cursor: pointer;
padding: 0.2rem 0.6rem;
}
</style>

View File

@@ -22,6 +22,11 @@
font-size: 1rem;
}
::placeholder {
color: #859a90;
opacity: 1;
}
input:focus {
border-color: #007bff;
outline: none;

View File

@@ -25,6 +25,11 @@
font-size: 1rem;
}
::placeholder {
color: #859a90;
opacity: 1;
}
textarea:focus {
border-color: #007bff;
outline: none;

View File

@@ -70,6 +70,7 @@ export type Database = {
description: string | null
id: string
location: string | null
tags: string[]
updated_at: string | null
}
Insert: {
@@ -78,6 +79,7 @@ export type Database = {
description?: string | null
id?: string
location?: string | null
tags?: string[]
updated_at?: string | null
}
Update: {
@@ -86,6 +88,7 @@ export type Database = {
description?: string | null
id?: string
location?: string | null
tags?: string[]
updated_at?: string | null
}
Relationships: []

View File

@@ -23,5 +23,6 @@ export const availableStudySpaceTags = [
"Hot",
"Air conditioned",
"Cold",
"Cringe"
"Cringe",
"PCs"
];

View File

@@ -7,7 +7,7 @@
import Button from "$lib/components/Button.svelte";
import Images from "$lib/components/inputs/Images.svelte";
import type { Table } from "$lib";
import { availableStudySpaceTags } from "$lib";
const { data } = $props();
const { supabase } = $derived(data);
@@ -16,7 +16,8 @@
let studySpaceData = $state<Omit<Table<"study_spaces">, "id" | "created_at" | "updated_at">>({
description: "",
building_location: "",
location: ""
location: "",
tags: []
});
async function uploadStudySpace() {
@@ -63,6 +64,31 @@
invalidate: ["db:study_spaces"]
});
}
// Tag
let tagFilter = $state("");
let tagFilterElem = $state<HTMLInputElement>();
let filteredTags = $derived(
availableStudySpaceTags
.filter((tag) => tag.toLowerCase().includes(tagFilter.toLowerCase()))
.filter((tag) => !studySpaceData.tags.includes(tag))
);
let dropdownVisible = $state(false);
function deleteTag(tagName: string) {
return () => {
studySpaceData.tags = studySpaceData.tags.filter((tag) => tag !== tagName);
};
}
function addTag(tagName: string) {
return () => {
if (!studySpaceData.tags.includes(tagName)) {
studySpaceData.tags.push(tagName);
}
tagFilter = "";
};
}
</script>
<Navbar>
@@ -89,13 +115,60 @@
required
/>
<label for="description">Add a description:</label>
<label for="tags">Add tags:</label>
<div class="tagDisplay">
{#each studySpaceData.tags as tagName (tagName)}
<button class="tag" onclick={deleteTag(tagName)} type="button">
{tagName}
<img src={crossUrl} alt="delete" /></button
>
{/each}
<input
type="text"
name="tagInput"
class="tagInput"
bind:value={tagFilter}
bind:this={tagFilterElem}
onfocus={() => {
dropdownVisible = true;
}}
onblur={() => {
dropdownVisible = false;
}}
onkeypress={(event) => {
if (event.key === "Enter") {
const tag = filteredTags[0];
if (tag) addTag(tag)();
}
}}
placeholder="Add tags..."
/>
{#if dropdownVisible}
<div class="tagDropdown">
{#each filteredTags as avaliableTag (avaliableTag)}
<button
class="avaliableTag"
onclick={addTag(avaliableTag)}
onmousedown={(e) => {
// Keep input focused
e.preventDefault();
e.stopPropagation();
}}
type="button"
>
{avaliableTag}
</button>
{/each}
</div>
{/if}
</div>
<label for="description">Optional brief description:</label>
<Textarea
name="description"
bind:value={studySpaceData.description}
placeholder="A quiet and well-lit, but small space..."
rows={5}
required
placeholder="A quiet room with a lovely view of the park."
rows={2}
/>
<label for="building-location">Add the building location:</label>
@@ -111,7 +184,7 @@
type="submit"
disabled={(spaceImgs?.length || 0) === 0 ||
!studySpaceData.location ||
!studySpaceData.description ||
studySpaceData.tags.length === 0 ||
!studySpaceData.building_location ||
uploading}
>
@@ -143,4 +216,82 @@
margin-left: -0.5rem;
width: calc(100% + 1rem);
}
.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;
}
</style>

View File

@@ -29,11 +29,21 @@
<div class="nameContainer">
{space.location}
</div>
<p class="descContainer">
{space.description}
</p>
{#if space.description != null && space.description.length > 0}
<p class="descContainer">
{space.description}
</p>
<hr />
{/if}
<div class="tagContainer">
{#each space.tags as tag (tag)}
<span class="tag">
{tag}
</span>
{/each}
</div>
<hr />
<div class="whereSubtitle">Where it is:</div>
<div class="subtitle">Where it is:</div>
<p class="addrContainer">
{space.building_location}
</p>
@@ -87,7 +97,7 @@
font-size: 1.2rem;
}
.whereSubtitle {
.subtitle {
font-size: 1.2rem;
font-weight: bold;
color: #ffffff;
@@ -98,4 +108,24 @@
font-size: 1.2rem;
padding: 0rem 1.4rem;
}
.tagContainer {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
padding: 1.4rem;
border-radius: 0.5rem;
background: none;
}
.tag {
display: flex;
align-items: center;
border-radius: 0.25rem;
background-color: #2e4653;
color: #eaffeb;
font-size: 1.1rem;
cursor: pointer;
padding: 0.2rem 0.6rem;
}
</style>

View File

@@ -0,0 +1,3 @@
alter table "public"."study_spaces" add column "tags" text[] not null default ARRAY[]::text[];