Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 114 additions & 53 deletions src/components/Course.vue
Original file line number Diff line number Diff line change
@@ -1,80 +1,104 @@
<script setup>
import { watch } from 'vue'
import { CheckIcon } from '@heroicons/vue/24/outline'
import { get } from '../firebase.js'
import state from '../model.js'
import Wrapper from './Wrapper.vue'
import Instructor from './Instructor.vue'
import LabelSwitch from './LabelSwitch.vue'
import { watch } from "vue";
import { CheckIcon } from "@heroicons/vue/24/outline";
import { get } from "../firebase.js";
import state from "../model.js";
import Wrapper from "./Wrapper.vue";
import Instructor from "./Instructor.vue";
import LabelSwitch from "./LabelSwitch.vue";

const props = defineProps({
modelValue: String,
quarter: {
type: String,
default: () => state.course.quarter,
}
})
const emits = defineEmits(['update:modelValue'])
},
});
const emits = defineEmits(["update:modelValue"]);

// control UI
const levels = { U: 'Undergraduate', G: 'Graduate', L: 'Lower Division', S: 'Upper Division' }
const gradings = { null: 'optional', L: 'Letter', P: 'P/NP' }
const levels = {
U: "Undergraduate",
G: "Graduate",
L: "Lower Division",
S: "Upper Division",
};
const gradings = { null: "optional", L: "Letter", P: "P/NP" };

let course = $ref({}), isSummer = $ref(false)
let title = $computed(() => props.modelValue ? props.modelValue + ': ' + (course.title || '') : 'Please select a course')
let course = $ref({}),
isSummer = $ref(false);
let title = $computed(() =>
props.modelValue
? props.modelValue + ": " + (course.title || "")
: "Please select a course",
);

let instructorName = $ref('')
function leaveInstructor () {
if (!state.screen.md) instructorName = ''
let instructorName = $ref("");
function leaveInstructor() {
if (!state.screen.md) instructorName = "";
}

watch(
() => [props.modelValue, props.quarter],
async ([v, q]) => {
if (!v) {
course = {}
isSummer = false
return
course = {};
isSummer = false;
return;
}

course = {}
isSummer = false
const quarter = q || state.course.quarter
course = {};
isSummer = false;
const quarter = q || state.course.quarter;

course = await get('course/' + quarter + v)
course = await get("course/" + quarter + v);

for (const s in course.sections) {
if (course.sections[s].session) {
isSummer = true
break
isSummer = true;
break;
}
}
setTimeout(() => { course.show = 1 })
setTimeout(() => {
course.show = 1;
});
},
{ immediate: true }
)


function toggleFocus () {
const k = props.modelValue
if (state.course.focus[k]) delete state.course.focus[k]
else state.course.focus[k] = 1
{ immediate: true },
);
// the Focus button on each course
function toggleFocus() {
const k = props.modelValue;
if (state.course.focus[k]) delete state.course.focus[k];
else state.course.focus[k] = 1;
}
</script>

<template>
<Transition name="fade">
<div v-if="state.screen.md && props.modelValue" @click="emits('update:modelValue', false)" class="fixed w-full h-screen bg-black opacity-30 z-50 top-0" />
<div
v-if="state.screen.md && props.modelValue"
@click="emits('update:modelValue', false)"
class="fixed w-full h-screen bg-black opacity-30 z-50 top-0"
/>
</Transition>
<div class="course bg-white p-4 sm:p-6 flex-grow all-transition
fixed top-0 h-screen w-5/6 sm:w-11/12 z-50 overflow-auto
md:sticky md:top-20 md:w-auto md:h-auto md:shadow-md md:z-10
" :style="{ left: state.screen.md && !props.modelValue ? '-100%' : '0px' }">
<div
class="course bg-white p-4 sm:p-6 flex-grow all-transition fixed top-0 h-screen w-5/6 sm:w-11/12 z-50 overflow-auto md:sticky md:top-20 md:w-auto md:h-auto md:shadow-md md:z-10"
:style="{ left: state.screen.md && !props.modelValue ? '-100%' : '0px' }"
>
<h2 class="text-xl sm:text-2xl font-bold mr-6">{{ title }}</h2>
<Wrapper :show="course.show" :key="props.modelValue">
<p class="text-sm text-gray-600">{{ course.college }} &nbsp; {{ course.department }} &nbsp; {{ levels[course.level] }}</p>
<button class="text-white text-sm shadow rounded-full bg-blue-500 px-4 py-1 my-2 flex items-center" @click="toggleFocus">
<CheckIcon class="all-transition overflow-x-hidden" :class="state.course.focus[props.modelValue] ? 'w-5 mr-1' : 'w-0'" />
<p class="text-sm text-gray-600">
{{ course.college }} &nbsp; {{ course.department }} &nbsp;
{{ levels[course.level] }}
</p>
<button
class="text-white text-sm shadow rounded-full bg-blue-500 px-4 py-1 my-2 flex items-center"
@click="toggleFocus"
>
<CheckIcon
class="all-transition overflow-x-hidden"
:class="state.course.focus[props.modelValue] ? 'w-5 mr-1' : 'w-0'"
/>
Focus
</button>
<p class="my-2">{{ course.description }}</p>
Expand All @@ -86,9 +110,13 @@ function toggleFocus () {
<LabelSwitch v-for="g in course.GE">{{ g }}</LabelSwitch>
<span v-if="!course.GE.length" class="px-1">N/A</span>
</p>
<hr class="my-3">
<hr class="my-3" />
<div class="w-full overflow-x-auto">
<table v-if="course.tree" class="w-full text-center" :style="{ minWidth: isSummer ? '560px' : '480px' }">
<table
v-if="course.tree"
class="w-full text-center"
:style="{ minWidth: isSummer ? '560px' : '480px' }"
>
<tr>
<th v-if="isSummer">Session</th>
<th>Code</th>
Expand All @@ -97,19 +125,52 @@ function toggleFocus () {
<th>Location</th>
</tr>
<template v-for="(ss, lec) in course.tree">
<tr class="bg-blue-100 border-white border-y-1" :set="s = course.sections[lec]"><!-- lecture -->
<tr
class="bg-blue-100 border-white border-y-1"
:set="s = course.sections[lec]"
>
<!-- lecture -->
<td class="text-sm" v-if="isSummer">{{ s.session }}</td>
<td :class="s.closed && 'line-through'">{{ lec }}</td>
<td><div v-for="i in s.instructors" @click="instructorName = i" @mouseenter="instructorName = i" @mouseleave="leaveInstructor">{{ i }}</div></td>
<td><div v-for="p in s.periods">{{ p.time }}</div></td>
<td><div v-for="p in s.periods">{{ p.location }}</div></td>
<td>
<div
v-for="i in s.instructors"
@click="instructorName = i"
@mouseenter="instructorName = i"
@mouseleave="leaveInstructor"
>
{{ i }}
</div>
</td>
<td>
<div v-for="p in s.periods">{{ p.time }}</div>
</td>
<td>
<div v-for="p in s.periods">{{ p.location }}</div>
</td>
</tr>
<tr class="opacity-60 bg-blue-100 border-white border border-x-0 all-transition" v-for="code in ss" :set="s = course.sections[code]">
<tr
class="opacity-60 bg-blue-100 border-white border border-x-0 all-transition"
v-for="code in ss"
:set="s = course.sections[code]"
>
<td class="text-sm" v-if="isSummer">{{ s.session }}</td>
<td :class="s.closed && 'line-through'">{{ code }}</td>
<td><div v-for="i in s.instructors" @mouseenter="instructorName = i" @mouseleave="instructorName = ''">{{ i }}</div></td>
<td><div v-for="p in s.periods">{{ p.time }}</div></td>
<td><div v-for="p in s.periods">{{ p.location }}</div></td>
<td>
<div
v-for="i in s.instructors"
@mouseenter="instructorName = i"
@mouseleave="instructorName = ''"
>
{{ i }}
</div>
</td>
<td>
<div v-for="p in s.periods">{{ p.time }}</div>
</td>
<td>
<div v-for="p in s.periods">{{ p.location }}</div>
</td>
</tr>
</template>
</table>
Expand Down
47 changes: 36 additions & 11 deletions src/views/Planner.vue
Original file line number Diff line number Diff line change
@@ -1,35 +1,52 @@
<script setup>
import { onActivated } from 'vue'
import { ArrowLeftIcon, CpuChipIcon, InformationCircleIcon } from '@heroicons/vue/24/outline'
import { get, log } from '../firebase.js'
import state from '../model.js'
import PanelWrapper from '../components/PanelWrapper.vue'
import Schedule from '../components/Schedule.vue'
import Instructor from '../components/Instructor.vue'
import { useRouter } from 'vue-router'
const router = useRouter(), focus = state.course.focus
const router = useRouter()
log('web/planner')

let loading = $ref(true), choices = $ref({}), chosenwTime = $ref([])
let focus = $ref({})
let isSummer = $computed(() => state.course?.quarter && state.course.quarter[4] === '3')

let instructorName = $ref('')
function leaveInstructor () {
if (!state.screen.md) instructorName = ''
}

const sections = {}, courses = [], conflicts = {}
let sections = $ref({}), courses = $ref([]), conflicts = $ref({})

async function fetchData () {
if (!focus) return router.push('/course')
for (const k in focus) if (!focus[k]) delete focus[k]
if (!Object.keys(focus).length) return router.push('/course')
for (const k in focus) {
const c = focus[k] = await get('course/' + state.course.quarter + k)
// read and filter course from the state.course.focus
const focusKeys = Object.keys(state.course.focus || {}).filter(k => state.course.focus[k])
// if no course focused, return user to the course page
if (!focusKeys.length) return router.push('/course')

const prevChoices = choices || {}
//reset the local
focus = {}
choices = {}
chosenwTime = []
sections = {}
courses = []
conflicts = {}

for (const k of focusKeys) {
const c = await get('course/' + state.course.quarter + k)
focus[k] = c
choices[k] = {}
// if the user already made selections for this course before reload, restore them. Otherwise start with an empty selection.
choices[k] = prevChoices[k] ? { ...prevChoices[k] } : {}
courses.push(k)
for (const s in c.sections) {
sections[s] = c.sections[s]
for (const p of sections[s].periods) p.wTime = JSON.parse(p.wTime)
for (const p of sections[s].periods) {
if (typeof p.wTime === 'string') p.wTime = JSON.parse(p.wTime)
}
}
}
const isOverlap = (x, y) => x[1] >= y[0] && x[0] <= y[1]
Expand All @@ -53,7 +70,13 @@ async function fetchData () {
computeStatus()
loading = false
}
fetchData()
// When user changes selected courses and returns to Planner, re-fetch and rebuild data on activation
async function init () {
loading = true
await fetchData()
}

onActivated(init)

function isConflict (_choices, s, exception = '') {
for (const k in _choices) {
Expand Down Expand Up @@ -141,6 +164,8 @@ let pieces = $computed(() => {
})

function auto () {
// ensure statuses are up-to-date before starting auto selection
computeStatus()
function scan () {
for (const k in choices) {
const c = choices[k]
Expand Down Expand Up @@ -196,7 +221,7 @@ function help () {
<div class="flex items-start overflow-x-auto" style="min-height: 70vh;">
<div class="overflow-x-auto shadow-md" :style="{ minWidth: isSummer ? '560px' : '480px' }">
<!-- course list -->
<PanelWrapper v-if="!loading" v-for="(v, k) in focus" :title="k" :class="choices[k]?.done ? 'bg-blue-100' : 'bg-gray-100'">
<PanelWrapper v-if="!loading" v-for="(v, k) in focus" :key="k" :title="k" :class="choices[k]?.done ? 'bg-blue-100' : 'bg-gray-100'">
<table v-if="v.tree" class="w-full text-center">
<tr>
<th v-if="isSummer">Session</th>
Expand Down