Files
drp-48/src/routes/space/[id]/+page.svelte
Caspar Jojo Asaam 950ab5193a merge: Resolved merge conflicts
Co-Authored-By: Tadios Temesgen <tt2022@ic.ac.uk>
2025-06-12 16:21:20 +01:00

282 lines
7.2 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts">
import Navbar from "$lib/components/Navbar.svelte";
import crossUrl from "$lib/assets/cross.svg";
import placeholder from "$lib/assets/study_space.png";
import Carousel from "$lib/components/Carousel.svelte";
import CompulsoryTags from "$lib/components/CompulsoryTags.svelte";
import Report from "$lib/components/Report.svelte";
import Feedback from "$lib/components/Feedback.svelte";
import { onMount } from "svelte";
import { gmapsLoader, daysOfWeek, formatTime } from "$lib";
import Button from "$lib/components/Button.svelte";
const { data } = $props();
const { space, supabase, adminMode } = $derived(data);
const place = $derived(space.building_location as google.maps.places.PlaceResult);
const imgUrls = $derived(
space.study_space_images.length === 0
? [placeholder]
: space.study_space_images.map(
(img) =>
supabase.storage.from("files_bucket").getPublicUrl(img.image_path).data
.publicUrl
)
);
let isReportVisible = $state(false);
function hideReport() {
isReportVisible = false;
}
let isFeedbackPromptVisible = $state(false);
function hideFeedbackPrompt() {
isFeedbackPromptVisible = false;
}
let mapElem = $state<HTMLDivElement>();
onMount(async () => {
if (!mapElem) return console.error("Map element not found");
const loader = await gmapsLoader();
const { Map } = await loader.importLibrary("maps");
const { AdvancedMarkerElement } = await loader.importLibrary("marker");
const map = new Map(mapElem, {
center: place.geometry?.location,
zoom: 15,
mapId: "9f4993cd3fb1504d495821a5"
});
new AdvancedMarkerElement({
position: place.geometry?.location,
map
});
});
const hoursByDay = $derived(new Map(space.study_space_hours.map((h) => [h.day_of_week, h])));
const openingEntries = daysOfWeek.map((day, idx) => ({
day,
entry: hoursByDay.get(idx)
}));
</script>
<Navbar>
<a href="/">
<img src={crossUrl} alt="close" />
</a>
</Navbar>
{#if isReportVisible}<Report {data} studySpaceId={space.id} hideFunc={hideReport} />
{/if}
{#if isFeedbackPromptVisible}
<Feedback
studySpaceData={{
...space,
building_location: place
}}
{supabase}
hideFunc={hideFeedbackPrompt}
/>
{/if}
<main>
<Carousel urls={imgUrls} />
<div class="nameContainer">
{space.location}
</div>
{#if space.description != null && space.description.length > 0}
<p class="descContainer">
{space.description}
</p>
<hr />
{/if}
<div class="compulsoryContainer"><CompulsoryTags {space} /></div>
{#if space.tags.length > 0}
<div class="tagContainer">
{#each space.tags as tag (tag)}
<span class="tag">
{tag}
</span>
{/each}
</div>
{/if}
<hr />
<div class="subtitle">Opening Times:</div>
{#each openingEntries as { day, entry } (entry)}
<div class="opening-entry">
<span class="day">{day}:</span>
<span class="times">
{#if entry}
{entry.is_24_7
? "Open All Day"
: `${formatTime(entry.opens_at)} ${formatTime(entry.closes_at)}`}
{:else}
Closed
{/if}
</span>
</div>
{/each}
<div class="subtitle">Where it is:</div>
<p class="addrContainer">
{#if place.name}
{place.name} <br />
{/if}
{#each place.formatted_address?.split(",") || [] as line (line)}
{line.trim()} <br />
{/each}
</p>
<div class="addrMap" bind:this={mapElem}></div>
<button
type="button"
class="feedbackButton"
onclick={() => {
isFeedbackPromptVisible = true;
}}
>
Help categorise this space
</button>
<div class="actions">
{#if adminMode}
<Button href="/space/{space.id}/edit" type="link">Edit</Button>
{:else}
<Button onclick={() => (isReportVisible = true)} style="red">Report</Button>
{/if}
</div>
</main>
<style>
main {
display: flex;
flex-direction: column;
padding: 0 0 1rem 0;
max-width: 32rem;
margin: 0 auto;
}
img {
display: block;
width: 100%;
aspect-ratio: 1/0.8;
object-fit: cover;
object-position: center;
}
hr {
height: 2px;
background-color: #2e3c42;
width: 70%;
border: none;
margin: 0 auto;
}
.nameContainer {
z-index: 10;
display: block;
width: 100%;
padding: 0.6rem;
margin-top: -0.5rem;
object-position: center;
background-color: #49bd85;
border-radius: 8px;
font-size: 2.8rem;
font-weight: bold;
color: #ffffff;
}
.descContainer {
display: block;
width: 100%;
padding: 1.4rem;
margin-top: -0.5rem;
object-position: center;
border-radius: 8px;
font-size: 1.2rem;
}
.subtitle {
font-size: 1.2rem;
font-weight: bold;
color: #ffffff;
padding: 1.4rem;
}
.addrContainer {
font-size: 1.2rem;
padding: 0rem 1.4rem 1rem;
}
.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;
}
.compulsoryContainer {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
padding: 1.4rem;
font-size: 1.3rem;
padding-bottom: 0;
}
.addrMap {
width: 100%;
aspect-ratio: 1 / 1;
border-radius: 0.5rem;
border: 2px solid #eaffeb;
}
.feedbackButton {
width: 100%;
padding: 0.7rem;
border-radius: 0.5rem;
border: none;
background-color: #49bd85;
color: #ffffff;
font-size: 1rem;
cursor: pointer;
margin-top: 1rem;
text-align: center;
}
.actions {
display: flex;
flex-direction: column;
padding-top: 1rem;
}
.opening-entry {
display: grid;
grid-template-columns: auto 1fr;
gap: 0.75rem;
padding: 0.5rem 1.4rem;
align-items: center;
}
.opening-entry .day {
font-weight: bold;
color: #ffffff;
white-space: nowrap;
}
.opening-entry .times {
font-family: monospace;
background-color: rgba(255, 255, 255, 0.1);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
color: #eaffeb;
}
</style>