Feat/open now #55
@@ -1,24 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CompulsoryTags from "./CompulsoryTags.svelte";
|
import CompulsoryTags from "./CompulsoryTags.svelte";
|
||||||
import OpeningTimes from "./OpeningTimes.svelte";
|
|
||||||
import Favourite from "./Favourite.svelte";
|
import Favourite from "./Favourite.svelte";
|
||||||
import type { Table } from "$lib";
|
import type { Table } from "$lib";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
space: Table<"study_spaces">;
|
space: Table<"study_spaces">;
|
||||||
hours: Table<"study_space_hours">[];
|
|
||||||
alt: string;
|
alt: string;
|
||||||
imgSrc: string;
|
imgSrc: string;
|
||||||
href?: string;
|
href?: string;
|
||||||
isFavourite: boolean;
|
isFavourite: boolean;
|
||||||
|
isAvailable?: boolean;
|
||||||
onToggleFavourite?: () => void;
|
onToggleFavourite?: () => void;
|
||||||
|
footer?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { space, hours, alt, imgSrc, href, isFavourite, onToggleFavourite }: Props = $props();
|
const { space, alt, imgSrc, href, isFavourite, onToggleFavourite, isAvailable, footer }: Props =
|
||||||
|
$props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a class="card" {href}>
|
<a class="card {isAvailable ? 'green' : 'grey'}" {href}>
|
||||||
<!-- <img src={imgSrc} {alt} /> -->
|
<!-- <img src={imgSrc} {alt} /> -->
|
||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
<img src={imgSrc} {alt} />
|
<img src={imgSrc} {alt} />
|
||||||
@@ -34,11 +34,14 @@
|
|||||||
{#if space.tags.length > 0}
|
{#if space.tags.length > 0}
|
||||||
<div class="tagContainer">
|
<div class="tagContainer">
|
||||||
{#each space.tags as tag (tag)}
|
{#each space.tags as tag (tag)}
|
||||||
<span class="tag">{tag}</span>
|
<span class="tag {isAvailable ? 'tagGreen' : 'tagGrey'}">{tag}</span>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="openingTimesContainer"><OpeningTimes {hours} /></div>
|
<div class="spacer"></div>
|
||||||
|
{#if footer}
|
||||||
|
<div class="footer">{footer}</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -46,14 +49,25 @@
|
|||||||
.card {
|
.card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: #49bd85;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 20rem;
|
max-width: 20rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
.green {
|
||||||
|
background-color: #49bd85;
|
||||||
|
}
|
||||||
|
.grey {
|
||||||
|
background-color: #2e4653;
|
||||||
|
}
|
||||||
|
.spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
.description {
|
.description {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
color: #edebe9;
|
color: #edebe9;
|
||||||
}
|
}
|
||||||
@@ -83,13 +97,20 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
background-color: #2e4653;
|
|
||||||
color: #eaffeb;
|
color: #eaffeb;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.2rem 0.6rem;
|
padding: 0.2rem 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tagGreen {
|
||||||
|
background-color: #2e4653;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagGrey {
|
||||||
|
background-color: #49bd85;
|
||||||
|
}
|
||||||
|
|
||||||
.compulsoryContainer {
|
.compulsoryContainer {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(3.75rem, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(3.75rem, 1fr));
|
||||||
@@ -109,4 +130,16 @@
|
|||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
width: 100%;
|
||||||
|
color: #2e4653;
|
||||||
|
background-color: #eaffeb;
|
||||||
|
align-self: flex-end;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0.1rem;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -84,3 +84,21 @@ export function haversineDistance(
|
|||||||
Math.pow(Math.sin(deltaLng / 2), 2) * Math.cos(lat1) * Math.cos(lat2);
|
Math.pow(Math.sin(deltaLng / 2), 2) * Math.cos(lat1) * Math.cos(lat2);
|
||||||
return radius * 2 * Math.asin(Math.sqrt(e1));
|
return radius * 2 * Math.asin(Math.sqrt(e1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function collectTimings(study_space_hours: Table<"study_space_hours">[]) {
|
||||||
|
// Collect all timing entries
|
||||||
|
const timingsPerDay: Record<number, Table<"study_space_hours">[]> = {
|
||||||
|
0: [],
|
||||||
|
1: [],
|
||||||
|
2: [],
|
||||||
|
3: [],
|
||||||
|
4: [],
|
||||||
|
5: [],
|
||||||
|
6: []
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const entry of study_space_hours) {
|
||||||
|
timingsPerDay[entry.day_of_week].push(entry);
|
||||||
|
}
|
||||||
|
return timingsPerDay;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
import crossUrl from "$lib/assets/cross.svg";
|
import crossUrl from "$lib/assets/cross.svg";
|
||||||
import searchUrl from "$lib/assets/search.svg";
|
import searchUrl from "$lib/assets/search.svg";
|
||||||
import Navbar from "$lib/components/Navbar.svelte";
|
import Navbar from "$lib/components/Navbar.svelte";
|
||||||
import { haversineDistance, timeToMins } from "$lib";
|
import { collectTimings, timeToMins, haversineDistance } from "$lib";
|
||||||
import Button from "$lib/components/Button.svelte";
|
import Button from "$lib/components/Button.svelte";
|
||||||
import { urldecodeSortFilter } from "$lib/filter.js";
|
import { urldecodeSortFilter } from "$lib/filter.js";
|
||||||
import { invalidateAll } from "$app/navigation";
|
import { invalidateAll } from "$app/navigation";
|
||||||
|
import type { Table } from "$lib";
|
||||||
|
|
||||||
const { data } = $props();
|
const { data } = $props();
|
||||||
const {
|
const {
|
||||||
@@ -123,6 +124,45 @@
|
|||||||
})
|
})
|
||||||
: filteredStudySpaces
|
: filteredStudySpaces
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Open now
|
||||||
|
function isOpenNow(all_study_space_hours: Table<"study_space_hours">[]) {
|
||||||
|
const now = new Date();
|
||||||
|
const time = now.toTimeString().slice(0, 5);
|
||||||
|
const day = now.getDay();
|
||||||
|
|
||||||
|
const timingsPerDay = collectTimings(all_study_space_hours);
|
||||||
|
for (const timing of timingsPerDay[day]) {
|
||||||
|
if (timing.open_today_status === true) {
|
||||||
|
return { isOpen: true, message: `Open all day` };
|
||||||
|
} else if (timing.open_today_status === false) {
|
||||||
|
return { isOpen: false, message: `Closed today` };
|
||||||
|
} else {
|
||||||
|
const opensFor = timeUntilClosing(timing.opens_at, timing.closes_at, time);
|
||||||
|
if (opensFor) {
|
||||||
|
return {
|
||||||
|
isOpen: true,
|
||||||
|
message: `Open now for: ${minsToReadableHours(timeToMins(time))}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isOpen: false, message: "Closed right now" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeUntilClosing(openingTime: string, closingTime: string, currentTime: string) {
|
||||||
|
const currTimeInMins = timeToMins(currentTime);
|
||||||
|
const OpeningTimeInMins = timeToMins(openingTime);
|
||||||
|
const closingTimeInMins = timeToMins(closingTime);
|
||||||
|
if (currTimeInMins >= OpeningTimeInMins && currTimeInMins < closingTimeInMins) {
|
||||||
|
return closingTimeInMins - currTimeInMins;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function minsToReadableHours(mins: number) {
|
||||||
|
return `${Math.floor(mins / 60)} hrs, ${mins % 60} mins`;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Navbar>
|
<Navbar>
|
||||||
@@ -161,9 +201,14 @@
|
|||||||
href="/space/{studySpace.id}"
|
href="/space/{studySpace.id}"
|
||||||
imgSrc={imgUrl}
|
imgSrc={imgUrl}
|
||||||
space={studySpace}
|
space={studySpace}
|
||||||
hours={studySpace.study_space_hours}
|
|
||||||
isFavourite={favouriteIds.includes(studySpace.id)}
|
isFavourite={favouriteIds.includes(studySpace.id)}
|
||||||
onToggleFavourite={session ? () => handleToggleFavourite(studySpace.id) : undefined}
|
onToggleFavourite={session ? () => handleToggleFavourite(studySpace.id) : undefined}
|
||||||
|
isAvailable={studySpace.study_space_hours.length === 0
|
||||||
|
? undefined
|
||||||
|
: isOpenNow(studySpace.study_space_hours).isOpen}
|
||||||
|
footer={studySpace.study_space_hours.length === 0
|
||||||
|
? undefined
|
||||||
|
: isOpenNow(studySpace.study_space_hours).message}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import Report from "$lib/components/Report.svelte";
|
import Report from "$lib/components/Report.svelte";
|
||||||
import Feedback from "$lib/components/Feedback.svelte";
|
import Feedback from "$lib/components/Feedback.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { gmapsLoader, daysOfWeek, formatTime, type Table } from "$lib";
|
import { gmapsLoader, daysOfWeek, formatTime, collectTimings } from "$lib";
|
||||||
import Button from "$lib/components/Button.svelte";
|
import Button from "$lib/components/Button.svelte";
|
||||||
import Favourite from "$lib/components/Favourite.svelte";
|
import Favourite from "$lib/components/Favourite.svelte";
|
||||||
|
|
||||||
@@ -52,20 +52,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Collect all timing entries
|
let timingsPerDay = collectTimings(space.study_space_hours);
|
||||||
let timingsPerDay: Record<number, Table<"study_space_hours">[]> = {
|
|
||||||
0: [],
|
|
||||||
1: [],
|
|
||||||
2: [],
|
|
||||||
3: [],
|
|
||||||
4: [],
|
|
||||||
5: [],
|
|
||||||
6: []
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const entry of space.study_space_hours) {
|
|
||||||
timingsPerDay[entry.day_of_week].push(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
let isFavourite = $state(false);
|
let isFavourite = $state(false);
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
|||||||
@@ -59,7 +59,6 @@
|
|||||||
let spaceImgs = $state<FileList>();
|
let spaceImgs = $state<FileList>();
|
||||||
let uploading = $state(false);
|
let uploading = $state(false);
|
||||||
function checkTimings() {
|
function checkTimings() {
|
||||||
console.log(studySpaceData.opening_times);
|
|
||||||
let cannotExist = [] as number[];
|
let cannotExist = [] as number[];
|
||||||
|
|
||||||
const opensAtMinsAll = timeToMins(allDays.opens_at);
|
const opensAtMinsAll = timeToMins(allDays.opens_at);
|
||||||
@@ -91,7 +90,6 @@
|
|||||||
|
|
||||||
function genTimings(studySpaceId: string) {
|
function genTimings(studySpaceId: string) {
|
||||||
const fullDayOfWeek = [0, 1, 2, 3, 4, 5, 6];
|
const fullDayOfWeek = [0, 1, 2, 3, 4, 5, 6];
|
||||||
console.log(studySpaceData.opening_times, allDays);
|
|
||||||
// all day only
|
// all day only
|
||||||
if (
|
if (
|
||||||
studySpaceData.opening_times.length === 0 &&
|
studySpaceData.opening_times.length === 0 &&
|
||||||
@@ -101,8 +99,8 @@
|
|||||||
return fullDayOfWeek.map((day) => ({
|
return fullDayOfWeek.map((day) => ({
|
||||||
study_space_id: studySpaceId,
|
study_space_id: studySpaceId,
|
||||||
day_of_week: day,
|
day_of_week: day,
|
||||||
opens_at: allDays.open_today_status == null ? allDays.opens_at : "00:00",
|
opens_at: allDays.open_today_status !== null ? allDays.opens_at : "00:00",
|
||||||
closes_at: allDays.open_today_status == null ? allDays.closes_at : "00:00",
|
closes_at: allDays.open_today_status !== null ? allDays.closes_at : "00:01",
|
||||||
open_today_status: allDays.open_today_status
|
open_today_status: allDays.open_today_status
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -114,16 +112,16 @@
|
|||||||
.map((h) => ({
|
.map((h) => ({
|
||||||
study_space_id: studySpaceId,
|
study_space_id: studySpaceId,
|
||||||
day_of_week: h.day_of_week,
|
day_of_week: h.day_of_week,
|
||||||
opens_at: h.opens_at,
|
opens_at: h.open_today_status === null ? h.opens_at : "00:00",
|
||||||
closes_at: h.closes_at,
|
closes_at: h.open_today_status === null ? h.closes_at : "00:01",
|
||||||
open_today_status: h.open_today_status
|
open_today_status: h.open_today_status
|
||||||
}))
|
}))
|
||||||
.concat(
|
.concat(
|
||||||
nonDefinedDays.map((day) => ({
|
nonDefinedDays.map((day) => ({
|
||||||
study_space_id: studySpaceId,
|
study_space_id: studySpaceId,
|
||||||
day_of_week: day,
|
day_of_week: day,
|
||||||
opens_at: allDays.opens_at,
|
opens_at: allDays.open_today_status === null ? allDays.opens_at : "00:00",
|
||||||
closes_at: allDays.closes_at,
|
closes_at: allDays.open_today_status === null ? allDays.closes_at : "00:01",
|
||||||
open_today_status: allDays.open_today_status
|
open_today_status: allDays.open_today_status
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -134,10 +132,8 @@
|
|||||||
if (!spaceImgs || spaceImgs.length < 1) return alert("Please select an image file.");
|
if (!spaceImgs || spaceImgs.length < 1) return alert("Please select an image file.");
|
||||||
if (!studySpaceData.building_location) return alert("Please select a building location.");
|
if (!studySpaceData.building_location) return alert("Please select a building location.");
|
||||||
|
|
||||||
const { opening_times, ...spacePayload } = studySpaceData;
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { opening_times: _, ...spacePayload } = studySpaceData;
|
||||||
// explicitly mark opening_times as used to avoid unused warning
|
|
||||||
void opening_times;
|
|
||||||
|
|
||||||
const { data: studySpaceInsert, error: studySpaceError } = await supabase
|
const { data: studySpaceInsert, error: studySpaceError } = await supabase
|
||||||
.from("study_spaces")
|
.from("study_spaces")
|
||||||
|
|||||||
Reference in New Issue
Block a user