Merge pull request #28 from mcgill-ecsess/office-hour

Office Hours + Cleanup codebase for schemas & $lib
This commit is contained in:
ECSESS VP Tech Dev
2025-07-19 17:34:57 +01:00
committed by GitHub
15 changed files with 149 additions and 100 deletions

View File

@@ -0,0 +1,42 @@
<script lang="ts">
import type { OhCMSResponse } from '$lib/schemas';
function parseTime(timeStr: string): number {
let a = timeStr.match(/^(\d{1,2})(?::(\d{2}))?(AM|PM)$/i);
if (!a) return 0;
let hours = parseInt(a[1], 10);
let minutes = parseInt(a[2] || '0', 10);
let period = a[3];
if (period.toUpperCase() === 'PM' && hours !== 12) hours += 12;
if (period.toUpperCase() === 'AM' && hours === 12) hours = 0;
return Number(hours * 60 + minutes); // total minutes since midnight
}
let { allOhs }: { allOhs: OhCMSResponse } = $props();
let sortedOHs = $state(
allOhs.sort((a, b) => {
return parseTime(a.startTime) - parseTime(b.startTime);
})
);
</script>
<div class="grid min-w-2/3 grid-cols-5 gap-2 place-self-center">
{#each ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'] as day}
<div>
<p>- {day} -</p>
{#each sortedOHs.filter((OH) => OH.day == day) as OH}
<div
class="
card w-64 grid h-38 grid-rows-[3fr_2fr_3fr] bg-ecsess-200 text-ecsess-black m-3 place-content-center rounded-xl
p-4 text-center"
>
<p class="text-lg">{OH.startTime} - {OH.endTime}</p>
<p class="text-2xl">{OH.member.name.split(" ")[0]}</p>
<p class="text-sm italic">{OH.member.position}</p>
</div>
{/each}
</div>
{/each}
</div>

44
src/lib/schemas.ts Normal file
View File

@@ -0,0 +1,44 @@
export type EventPost = {
id: string;
title: string;
description: string;
date: string;
time: string;
location: string;
image: string;
link: string;
category: string;
payment: string; // event payment link (e.g., Zeffy)
};
import type { InputValue } from '@portabletext/svelte';
export type HomepageCMSResponse = {
description: InputValue;
councilPhoto: string;
faqs: {
question: string;
answer: string;
}[];
};
export type OhCMSResponse = {
day: string;
startTime: string;
endTime: string;
member: {
name: string;
position: string;
};
}[];
export type CouncilMember = {
name: string;
email: string;
position: string;
positionDescription: string;
image: string; // URL
yearProgram: string;
};
export type Redirect = { shortname: string; url: string };

View File

@@ -1,25 +0,0 @@
/**
* @typedef {Object} EventPost event object
* @property {string} id - event id
* @property {string} title - event title
* @property {string} description - event description
* @property {string} date - event date
* @property {string} time - event time
* @property {string} location - event location
* @property {string} image - event image
* @property {string} link - event link
* @property {string} category - event category
* @property {string} payment - event payment link (e.g., Zeffy)3
*/
/**
* @typedef {Object} CouncilMember
* @property {string} role
* @property {string} name
* @property {string} email
* @property {string} image
* @property {string} major
* @property {string} year
*/
export {};

View File

@@ -1,39 +0,0 @@
import { getFromCMS } from 'utils/utils.js';
const homepageQuery = `*[_type == "homepage"]{
"description": description[],
"councilPhoto": councilPhoto.asset->url,
"faqs": faqs[]{ question, answer },
}[0]`;
const ohQuery = `*[_type=="oh"].schedule[]{
day,
startTime,
endTime,
"host": member->name
}`;
export const load = async () => {
/**
* @description Response data type based on the `homepageQuery` above.
* Note that `description` is a rich/portable text type
*
* @type {{
* description: import('@portabletext/svelte').InputValue,
* councilPhoto: string,
* faqs: [{
* question: string,
* answer: string
* }],
* }}
*
*/
let CMSresponse = await getFromCMS(homepageQuery);
return {
description: CMSresponse.description,
councilPhoto: CMSresponse.councilPhoto,
faqs: CMSresponse.faqs
// ohs: await getFromCMS(ohQuery),
};
};

View File

@@ -0,0 +1,34 @@
import { getFromCMS } from '$lib/utils.js';
import type { HomepageCMSResponse, OhCMSResponse } from '$lib/schemas';
const homepageQuery = `*[_type == "homepage"]{
"description": description[],
"councilPhoto": councilPhoto.asset->url,
"faqs": faqs[]{ question, answer },
}[0]`;
const ohQuery = `*[_type=="officeHours"]{
day,
startTime,
endTime,
"member": {
"name": member->name,
"position": member->position
}
}`;
export const load = async () => {
/**
* @description Response data type based on the `homepageQuery` above.
* Note that `description` is a rich/portable text type
*/
let homepageResp: HomepageCMSResponse = await getFromCMS(homepageQuery);
let officeHourResp: OhCMSResponse = await getFromCMS(ohQuery);
return {
description: homepageResp.description,
councilPhoto: homepageResp.councilPhoto,
faqs: homepageResp.faqs,
allOHs: officeHourResp
};
};

View File

@@ -2,6 +2,7 @@
import FaqAccordion from 'components/FAQAccordion.svelte'; import FaqAccordion from 'components/FAQAccordion.svelte';
import Section from 'components/Section.svelte'; import Section from 'components/Section.svelte';
import { PortableText } from '@portabletext/svelte'; import { PortableText } from '@portabletext/svelte';
import OhSchedule from 'components/OHSchedule.svelte';
/** loading things from the server side */ /** loading things from the server side */
let { data } = $props(); let { data } = $props();
@@ -46,6 +47,6 @@
<Section> <Section>
<div> <div>
<h1 class="text-2xl">Office Hours</h1> <h1 class="text-2xl">Office Hours</h1>
<p>Under development</p> <OhSchedule allOhs={data.allOHs}/>
</div> </div>
</Section> </Section>

View File

@@ -1,4 +1,4 @@
import { getFromCMS } from 'utils/utils.js'; import { getFromCMS } from '$lib/utils.js';
const query = `*[_type == "members"]{ const query = `*[_type == "members"]{
name, name,

View File

@@ -34,7 +34,7 @@
selectedMember = member; selectedMember = member;
} }
console.log(ureps) console.log(ureps);
// svelte-ignore state_referenced_locally // svelte-ignore state_referenced_locally
// console.log(selectedMember); // console.log(selectedMember);

View File

@@ -1,4 +1,4 @@
import { getFromCMS } from 'utils/utils.js'; import { getFromCMS } from '$lib/utils.js';
// needs to concat and format this text // needs to concat and format this text
const eventQuery = `*[_type == "events"]{ const eventQuery = `*[_type == "events"]{

View File

@@ -1,23 +0,0 @@
import { redirect } from '@sveltejs/kit';
import { getFromCMS } from 'utils/utils.js';
const redirectQuery = `*[_type == "redirects"]{ shortname, url }`;
export const load = async ({ params }) => {
/** @type {[{shortname: String, url: String}]} */
let CMSresponse = await getFromCMS(redirectQuery);
const { shortname } = params;
CMSresponse.forEach(res => {
if(res.shortname == shortname) {
// if match
throw redirect(302, res.url);
}
});
return {
shortname: shortname,
availableShortnames: CMSresponse,
}
};

View File

@@ -0,0 +1,23 @@
import { redirect } from '@sveltejs/kit';
import { getFromCMS } from '$lib/utils.js';
import type { Redirect } from '$lib/schemas';
const redirectQuery = `*[_type == "redirects"]{ shortname, url }`;
export const load = async ({ params }) => {
let CMSresponse: Redirect[] = await getFromCMS(redirectQuery);
const { shortname } = params;
CMSresponse.forEach((res) => {
if (res.shortname == shortname) {
// if match
throw redirect(302, res.url);
}
});
return {
shortname: shortname,
availableShortnames: CMSresponse
};
};

View File

@@ -1,4 +1,4 @@
import { getFromCMS } from 'utils/utils.js'; import { getFromCMS } from '$lib/utils.js';
// needs to concat and format this text // needs to concat and format this text
const query = `*[_type == "resources"]{ const query = `*[_type == "resources"]{

View File

@@ -1,8 +0,0 @@
export default interface CouncilMember {
name: string;
email: string;
position: string;
positionDescription: string;
image: string; // URL
yearProgram: string;
}