Add people + Move fetching to client side
This commit is contained in:
Generated
+6566
-139
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -38,6 +38,7 @@
|
||||
"@rollup/plugin-commonjs": "^14.0.0",
|
||||
"@rollup/plugin-node-resolve": "^8.0.0",
|
||||
"@rollup/plugin-replace": "^2.4.2",
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
"@tailwindcss/typography": "^0.3.1",
|
||||
"carbon-icons-svelte": "^10.21.0",
|
||||
"dotenv": "^10.0.0",
|
||||
@@ -56,4 +57,4 @@
|
||||
"tailwindcss": "^2.1.2",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-4
@@ -6,6 +6,7 @@ import babel from '@rollup/plugin-babel';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import config from 'sapper/config/rollup.js';
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import json from '@rollup/plugin-json';
|
||||
import sapperEnv from 'sapper-environment';
|
||||
|
||||
@@ -13,6 +14,7 @@ import pkg from './package.json';
|
||||
|
||||
const mode = process.env.NODE_ENV;
|
||||
const dev = mode === 'development';
|
||||
const sourcemap = dev ? 'inline' : false;
|
||||
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
|
||||
|
||||
const onwarn = (warning, onwarn) => {
|
||||
@@ -26,7 +28,7 @@ const onwarn = (warning, onwarn) => {
|
||||
);
|
||||
};
|
||||
|
||||
const preprocess = sveltePreprocess({ postcss: true });
|
||||
const preprocess = sveltePreprocess({ postcss: true, sourceMap: !!sourcemap });
|
||||
|
||||
export default {
|
||||
client: {
|
||||
@@ -39,6 +41,10 @@ export default {
|
||||
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||
preventAssignment: true,
|
||||
}),
|
||||
typescript({
|
||||
noEmitOnError: !dev,
|
||||
sourceMap: !!sourcemap,
|
||||
}),
|
||||
json(),
|
||||
svelte({
|
||||
preprocess,
|
||||
@@ -50,7 +56,9 @@ export default {
|
||||
browser: true,
|
||||
dedupe: ['svelte'],
|
||||
}),
|
||||
commonjs(),
|
||||
commonjs({
|
||||
sourceMap: !!sourcemap,
|
||||
}),
|
||||
|
||||
legacy &&
|
||||
babel({
|
||||
@@ -88,13 +96,17 @@ export default {
|
||||
|
||||
server: {
|
||||
input: config.server.input(),
|
||||
output: config.server.output(),
|
||||
output: { ...config.server.output(), sourcemap },
|
||||
plugins: [
|
||||
replace({
|
||||
'process.browser': false,
|
||||
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||
preventAssignment: true,
|
||||
}),
|
||||
typescript({
|
||||
noEmitOnError: !dev,
|
||||
sourceMap: !!sourcemap,
|
||||
}),
|
||||
svelte({
|
||||
preprocess,
|
||||
generate: 'ssr',
|
||||
@@ -104,7 +116,9 @@ export default {
|
||||
resolve({
|
||||
dedupe: ['svelte'],
|
||||
}),
|
||||
commonjs(),
|
||||
commonjs({
|
||||
sourceMap: !!sourcemap,
|
||||
}),
|
||||
],
|
||||
external: Object.keys(pkg.dependencies).concat(
|
||||
require('module').builtinModules
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
}
|
||||
|
||||
async function getExercises() {
|
||||
if (!lessonId) return;
|
||||
const exercisesData = await supabase
|
||||
.from('exercise')
|
||||
.select(`id, title, created_at`)
|
||||
@@ -125,7 +126,7 @@
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<PageBody width="w-11/12 m-auto">
|
||||
<PageBody>
|
||||
<slot:fragment slot="header">
|
||||
<PrimaryButton
|
||||
className="mr-2"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import CheckmarkFilled20 from 'carbon-icons-svelte/lib/CheckmarkFilled20';
|
||||
import { stores, goto } from '@sapper/app';
|
||||
import hotkeys from 'hotkeys-js';
|
||||
import CheckmarkFilled20 from 'carbon-icons-svelte/lib/CheckmarkFilled20';
|
||||
import Settings20 from 'carbon-icons-svelte/lib/Settings20';
|
||||
import ChevronLeft32 from 'carbon-icons-svelte/lib/ChevronLeft32';
|
||||
import ChevronRight32 from 'carbon-icons-svelte/lib/ChevronRight32';
|
||||
import { stores, goto } from '@sapper/app';
|
||||
import Expandable from '../../../Expandable/index.svelte';
|
||||
import PageNav from '../../../PageNav/index.svelte';
|
||||
import IconButton from '../../../IconButton/index.svelte';
|
||||
@@ -14,6 +15,8 @@
|
||||
getLectureNo,
|
||||
} from '../../function';
|
||||
|
||||
import Settings from '../Settings/index.svelte';
|
||||
import { settingsDialog } from '../Settings/store';
|
||||
import { lessons } from '../Lesson/store/lessons';
|
||||
import { course } from '../../store';
|
||||
import { updateCourse } from '../../../../utils/services/courses';
|
||||
@@ -78,32 +81,42 @@
|
||||
to: getNavItemRoute($course.id, 'submissions'),
|
||||
hideSortIcon: true,
|
||||
},
|
||||
{
|
||||
label: 'Scoreboard',
|
||||
to: getNavItemRoute($course.id, 'scoreboard'),
|
||||
hideSortIcon: true,
|
||||
},
|
||||
// {
|
||||
// label: 'Scoreboard',
|
||||
// to: getNavItemRoute($course.id, 'scoreboard'),
|
||||
// hideSortIcon: true,
|
||||
// },
|
||||
{
|
||||
label: 'People',
|
||||
to: getNavItemRoute($course.id, 'people'),
|
||||
hideSortIcon: true,
|
||||
},
|
||||
{
|
||||
label: 'Timetable',
|
||||
to: getNavItemRoute($course.id, 'timetable'),
|
||||
hideSortIcon: true,
|
||||
},
|
||||
// {
|
||||
// label: 'Timetable',
|
||||
// to: getNavItemRoute($course.id, 'timetable'),
|
||||
// hideSortIcon: true,
|
||||
// },
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<Settings />
|
||||
<div class="root z-10 {!show && 'hide'}">
|
||||
{#if show}
|
||||
<PageNav
|
||||
bind:title={$course.title}
|
||||
onEditComplete={handleSaveTitle}
|
||||
isTitleEditable={true}
|
||||
/>
|
||||
paddingClass="pl-2"
|
||||
>
|
||||
<slot:fragment slot="widget">
|
||||
<IconButton
|
||||
onClick={() => ($settingsDialog.open = !$settingsDialog.open)}
|
||||
>
|
||||
<Settings20 class="carbon-icon" />
|
||||
</IconButton>
|
||||
</slot:fragment>
|
||||
</PageNav>
|
||||
<div>
|
||||
{#each navItems as navItem}
|
||||
<Expandable
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<script>
|
||||
import Modal from '../../../Modal/index.svelte';
|
||||
import PrimaryButton from '../../../PrimaryButton/index.svelte';
|
||||
import TextArea from '../../../Form/TextArea.svelte';
|
||||
import { ROLE } from '../../../../utils/constants/roles';
|
||||
import {
|
||||
addGroupMember,
|
||||
fetchGroup,
|
||||
} from '../../../../utils/services/courses';
|
||||
import { invitationModal } from './store';
|
||||
import { validateForm } from '../../../Courses/functions';
|
||||
import { course, setCourseData } from '../../../Course/store';
|
||||
|
||||
let errors = {
|
||||
emails: '',
|
||||
};
|
||||
|
||||
function addPerson() {
|
||||
const { hasError, fieldErrors, students, tutors } = validateForm({
|
||||
...$invitationModal,
|
||||
title: 'sfdsd',
|
||||
description: 'dsfdsf',
|
||||
});
|
||||
|
||||
errors = fieldErrors;
|
||||
if (hasError) return;
|
||||
|
||||
let membersStack = [];
|
||||
console.log(`$course`, $course);
|
||||
// Create groupmemebers from group_id and add teachers and students
|
||||
for (const student of students) {
|
||||
membersStack.push({
|
||||
email: student,
|
||||
group_id: $course.group.id,
|
||||
role_id: ROLE.STUDENT,
|
||||
});
|
||||
}
|
||||
|
||||
for (const tutor of tutors) {
|
||||
membersStack.push({
|
||||
email: tutor,
|
||||
group_id: $course.group.id,
|
||||
role_id: ROLE.TUTOR,
|
||||
});
|
||||
}
|
||||
|
||||
addGroupMember(membersStack).then(async (membersAdded) => {
|
||||
console.log(`membersAdded`, membersAdded);
|
||||
const group = await fetchGroup($course.group.id);
|
||||
|
||||
setCourseData(
|
||||
{
|
||||
...$course,
|
||||
group: group.data,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
$invitationModal.open = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
onClose={() => ($invitationModal.open = false)}
|
||||
bind:open={$invitationModal.open}
|
||||
width="w-2/5"
|
||||
modalHeading="Create a Course"
|
||||
>
|
||||
<form on:submit|preventDefault={addPerson}>
|
||||
<TextArea
|
||||
label="Invite Tutors"
|
||||
bind:value={$invitationModal.tutors}
|
||||
rows="2"
|
||||
maxRows="3"
|
||||
placeholder="tutor@company.com, john@gmail.com"
|
||||
helperMessage="To invite people, add their emails separated by a comma"
|
||||
errorMessage={errors.tutors}
|
||||
className="mb-4"
|
||||
/>
|
||||
<TextArea
|
||||
label="Invite Students"
|
||||
bind:value={$invitationModal.students}
|
||||
rows="2"
|
||||
maxRows="3"
|
||||
placeholder="student@gmail.com, john@gmail.com"
|
||||
helperMessage="To invite people, add their emails separated by a comma"
|
||||
errorMessage={errors.students}
|
||||
className="mb-4"
|
||||
/>
|
||||
|
||||
<div class="mt-5 flex items-center">
|
||||
<PrimaryButton className="px-6 py-2" label="Finish" type="submit" />
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const invitationModal = writable({
|
||||
open: false,
|
||||
tutors: '',
|
||||
students: '',
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
export interface Profile {
|
||||
id: string;
|
||||
fullname: string;
|
||||
username: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface Person {
|
||||
id: string;
|
||||
email?: string;
|
||||
role_id: number;
|
||||
profile?: Profile;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<script>
|
||||
import Modal from '../../../Modal/index.svelte';
|
||||
import TextArea from '../../../Form/TextArea.svelte';
|
||||
import PrimaryButton from '../../../PrimaryButton/index.svelte';
|
||||
|
||||
import { VARIANTS } from '../../../PrimaryButton/constants';
|
||||
import { settingsDialog } from './store';
|
||||
import { course } from '../../store';
|
||||
import {
|
||||
updateCourse,
|
||||
deleteCourse,
|
||||
} from '../../../../utils/services/courses';
|
||||
|
||||
let shouldDelete = false;
|
||||
|
||||
async function updateDescription() {
|
||||
console.log('update');
|
||||
|
||||
$settingsDialog.open = false;
|
||||
await updateCourse($course.id, {
|
||||
description: $course.description,
|
||||
});
|
||||
}
|
||||
|
||||
async function handleDelete() {
|
||||
console.log('delete');
|
||||
|
||||
$settingsDialog.open = false;
|
||||
await deleteCourse($course.id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
onClose={() => ($settingsDialog.open = false)}
|
||||
bind:open={$settingsDialog.open}
|
||||
width="w-96"
|
||||
modalHeading="Update Course"
|
||||
>
|
||||
{#if shouldDelete}
|
||||
<form on:submit|preventDefault={() => (shouldDelete = false)}>
|
||||
<h1 class="text-2xl">Are you sure?</h1>
|
||||
|
||||
<div class="mt-5 flex items-center justify-between">
|
||||
<PrimaryButton
|
||||
className="px-6 py-2"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
label="No, cancel"
|
||||
type="submit"
|
||||
/>
|
||||
<PrimaryButton
|
||||
className="px-6 py-2"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
label="Yes, delete"
|
||||
onClick={handleDelete}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
{:else}
|
||||
<form on:submit|preventDefault={updateDescription}>
|
||||
<TextArea
|
||||
label="Short description"
|
||||
bind:value={$course.description}
|
||||
rows="4"
|
||||
maxRows="4"
|
||||
placeholder="A little description about this course"
|
||||
className="mb-4"
|
||||
isRequired={true}
|
||||
/>
|
||||
|
||||
<div class="mt-5 flex items-center justify-between">
|
||||
<PrimaryButton
|
||||
className="px-6 py-2"
|
||||
variant={VARIANTS.CONTAINED_DANGER}
|
||||
label="Delete"
|
||||
onClick={() => (shouldDelete = true)}
|
||||
/>
|
||||
<PrimaryButton className="px-6 py-2" label="Update" type="submit" />
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
</Modal>
|
||||
@@ -0,0 +1,5 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const settingsDialog = writable({
|
||||
open: false,
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
import PageNav from '../PageNav/index.svelte';
|
||||
import MODES from '../../utils/constants/mode';
|
||||
import PrimaryButton from '../PrimaryButton/index.svelte';
|
||||
import PageBody from '../PageBody/index.svelte';
|
||||
import EditContent from '../EditContent/index.svelte';
|
||||
import { course } from '../Course/store';
|
||||
import { updateCourse } from '../../utils/services/courses';
|
||||
@@ -37,7 +38,7 @@
|
||||
</svelte:fragment>
|
||||
</PageNav>
|
||||
|
||||
<div class="px-3">
|
||||
<PageBody>
|
||||
{#if mode === MODES.edit}
|
||||
<EditContent
|
||||
writeLabel="Note"
|
||||
@@ -51,4 +52,4 @@
|
||||
{:else}
|
||||
{@html marked($course.overview || '', { breaks: true, gfm: true })}
|
||||
{/if}
|
||||
</div>
|
||||
</PageBody>
|
||||
|
||||
@@ -26,9 +26,9 @@ export const group = writable({
|
||||
students: [],
|
||||
});
|
||||
|
||||
export async function setCourseData(data) {
|
||||
export async function setCourseData(data, setLesson = true) {
|
||||
if (!data || !(Object.values(data) && Object.values(data).length)) return;
|
||||
console.log(`data`, data);
|
||||
console.log(`setCourseData`, data);
|
||||
|
||||
if (data.group) {
|
||||
const groupData = Object.assign(data.group, { tutors: [], students: [] });
|
||||
@@ -37,7 +37,8 @@ export async function setCourseData(data) {
|
||||
for (const member of groupData.members) {
|
||||
if (member.role_id === ROLE.STUDENT) {
|
||||
groupData.students.push(member);
|
||||
} else if (member.profile) {
|
||||
// } else if (member.profile) {
|
||||
} else {
|
||||
groupData.tutors.push({
|
||||
...member,
|
||||
...member.profile,
|
||||
@@ -51,12 +52,13 @@ export async function setCourseData(data) {
|
||||
group.set(groupData);
|
||||
}
|
||||
|
||||
const orderedLessons = (data.lessons || []).sort(
|
||||
(a, b) => new Date(a.created_at) - new Date(b.created_at)
|
||||
);
|
||||
// .map();
|
||||
|
||||
lessons.set(orderedLessons);
|
||||
if (setLesson) {
|
||||
const orderedLessons = (data.lessons || []).sort(
|
||||
(a, b) => new Date(a.created_at) - new Date(b.created_at)
|
||||
);
|
||||
// .map();
|
||||
lessons.set(orderedLessons);
|
||||
}
|
||||
|
||||
delete data.lessons;
|
||||
course.set(data);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { course } from '../Course/store';
|
||||
import Confetti from '../Confetti/index.svelte';
|
||||
|
||||
export let path;
|
||||
export let path = '';
|
||||
export let isExercisePage = false;
|
||||
|
||||
function setStoreWithData() {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script>
|
||||
// import
|
||||
// border border-gray-300 rounded-md p-3 bg-blue-200 bg-opacity-25
|
||||
export let width = 'max-w-3xl';
|
||||
export let width = 'max-w-3xl w-11/12';
|
||||
export let className = '';
|
||||
</script>
|
||||
|
||||
<div class="root mt-4 mx-auto {width} relative pb-5 pr-5">
|
||||
<div class="root mt-4 mx-auto {width} {className} relative pb-5">
|
||||
{#if $$slots.header}
|
||||
<div
|
||||
class="head flex bg-white items-center justify-between sticky right-0 w-full py-3"
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
export let title = '';
|
||||
export let overidableStyle = '';
|
||||
export let navItems = [];
|
||||
export let addButtonHref;
|
||||
export let addButtonLabel;
|
||||
export let addButtonHref = '';
|
||||
export let addButtonLabel = '';
|
||||
export let disableSticky = false;
|
||||
export let isTitleEditable = false;
|
||||
export let onEditComplete = () => {};
|
||||
export let paddingClass = 'px-2';
|
||||
|
||||
let dynamicRootClass = '';
|
||||
let enterEditTitleMode = false;
|
||||
@@ -19,7 +20,7 @@
|
||||
|
||||
<div
|
||||
class="header flex items-center justify-between {!disableSticky &&
|
||||
'sticky'} {dynamicRootClass}"
|
||||
'sticky'} {paddingClass} {dynamicRootClass}"
|
||||
style={overidableStyle}
|
||||
>
|
||||
{#if !!title}
|
||||
@@ -66,7 +67,6 @@
|
||||
<style>
|
||||
.header {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 0 15px;
|
||||
min-height: 61px;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
export const VARIANTS = {
|
||||
CONTAINED: "CONTAINED",
|
||||
OUTLINED: "OUTLINED",
|
||||
CONTAINED: 'CONTAINED',
|
||||
CONTAINED_DANGER: 'CONTAINED_DANGER',
|
||||
OUTLINED: 'OUTLINED',
|
||||
};
|
||||
|
||||
export const VARIANTS_CLASS = {
|
||||
[VARIANTS.CONTAINED]: "bg-blue-700 hover:bg-blue-900 text-white font-bold",
|
||||
[VARIANTS.CONTAINED]: 'bg-blue-700 hover:bg-blue-900 text-white font-bold',
|
||||
[VARIANTS.CONTAINED_DANGER]:
|
||||
'bg-red-700 hover:bg-red-900 text-white font-bold',
|
||||
[VARIANTS.OUTLINED]:
|
||||
"border border-gray-700 hover:border-black-300 hover:bg-gray-200 text-black",
|
||||
'border border-gray-700 hover:border-black-300 hover:bg-gray-200 text-black',
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { handleAuthChange } from '../utils/functions/api';
|
||||
import { user, profile } from '../utils/store/user';
|
||||
import { getSupabase } from '../utils/functions/supabase';
|
||||
import { setProfileIdOfGroupMember } from '../utils/services/courses';
|
||||
|
||||
export let segment;
|
||||
export let config;
|
||||
@@ -52,6 +53,7 @@
|
||||
id: authUser.id,
|
||||
username: regexUsernameMatch[1],
|
||||
fullname: regexUsernameMatch[1],
|
||||
email: authUser.email,
|
||||
});
|
||||
|
||||
// Profile created, go to profile page
|
||||
@@ -61,6 +63,7 @@
|
||||
$user.currentSession = authUser;
|
||||
|
||||
profile.set(data[0]);
|
||||
await setProfileIdOfGroupMember(authUser.email, data[0].id);
|
||||
|
||||
// If user coming to login page, then
|
||||
if (path.includes('login') || path.includes('signup')) {
|
||||
@@ -75,6 +78,8 @@
|
||||
console.log(`profileData`, profileData);
|
||||
profile.set(profileData);
|
||||
|
||||
setProfileIdOfGroupMember(authUser.email, profileData.id);
|
||||
|
||||
// If user coming to login page, then
|
||||
if (path.includes('login') || path.includes('signup')) {
|
||||
goto('/dashboard');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script context="module">
|
||||
import { profile, user } from '../utils/store/user';
|
||||
import { profile } from '../utils/store/user';
|
||||
import { fetchCourses } from '../components/Courses/api';
|
||||
|
||||
export async function preload(page, params) {
|
||||
@@ -29,6 +29,7 @@
|
||||
import { courses, createCourseModal } from '../components/Courses/store';
|
||||
import { validateForm } from '../components/Courses/functions';
|
||||
import { ROLE } from '../utils/constants/roles';
|
||||
import { addGroupMember } from '../utils/services/courses';
|
||||
|
||||
// const { page } = stores();
|
||||
|
||||
@@ -69,8 +70,9 @@
|
||||
// Get profile of author and add as member
|
||||
const admin = await addGroupMember({
|
||||
profile_id: $profile.id,
|
||||
email: student,
|
||||
group_id,
|
||||
role_id: ROLE.ADMIN,
|
||||
role_id: ROLE.TUTOR,
|
||||
});
|
||||
|
||||
let membersStack = [];
|
||||
@@ -97,12 +99,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
function addGroupMember(member) {
|
||||
return supabase.from('groupmember').insert(member, {
|
||||
returning: 'minimal',
|
||||
});
|
||||
}
|
||||
|
||||
async function getCourses(userId) {
|
||||
console.log(`getCourses process.browser`, process.browser);
|
||||
if (process.browser) {
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
<script context="module">
|
||||
import { fetchCourse } from '../../../utils/services/courses';
|
||||
|
||||
export async function preload({ params }, session) {
|
||||
const {
|
||||
data,
|
||||
// error
|
||||
} = await fetchCourse(params.id, session);
|
||||
|
||||
return { courseData: data };
|
||||
return { courseId: params.id };
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,12 +8,17 @@
|
||||
import { onMount } from 'svelte';
|
||||
import Overview from '../../../components/Course/index.svelte';
|
||||
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
||||
import { setCourseData } from '../../../components/Course/store';
|
||||
import { setCourseData, course } from '../../../components/Course/store';
|
||||
import { fetchCourse } from '../../../utils/services/courses';
|
||||
|
||||
export let courseData;
|
||||
export let courseId;
|
||||
|
||||
onMount(async () => {
|
||||
setCourseData(courseData);
|
||||
console.log(`overview courseId`, courseId);
|
||||
if ($course.id) return;
|
||||
|
||||
const { data } = await fetchCourse(courseId);
|
||||
setCourseData(data);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,35 +3,10 @@
|
||||
fetchCourse,
|
||||
fetchLesson,
|
||||
} from '../../../../../utils/services/courses';
|
||||
let prevLessonId;
|
||||
|
||||
export async function preload({ params }, session) {
|
||||
const { id: courseId, lessonParams = [] } = params;
|
||||
const [lessonId, exerciseRouteName, exerciseId] = lessonParams;
|
||||
let courseData;
|
||||
let lessonData;
|
||||
|
||||
if (!process.browser) {
|
||||
const [course, lesson] = await Promise.all([
|
||||
fetchCourse(courseId, session),
|
||||
fetchLesson(lessonId),
|
||||
]);
|
||||
|
||||
courseData = course.data;
|
||||
lessonData = lesson.data;
|
||||
} else if (prevLessonId && prevLessonId !== lessonId) {
|
||||
const lesson = await fetchLesson(lessonId);
|
||||
|
||||
lessonData = lesson.data;
|
||||
}
|
||||
|
||||
prevLessonId = lessonId;
|
||||
return {
|
||||
courseData,
|
||||
lessonData,
|
||||
isMaterialsTabActive: !exerciseRouteName,
|
||||
lessonId,
|
||||
exerciseId,
|
||||
params,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
@@ -47,21 +22,54 @@
|
||||
import Materials from '../../../../../components/Course/components/Lesson/Materials/index.svelte';
|
||||
import Exercises from '../../../../../components/Course/components/Lesson/Exercises/index.svelte';
|
||||
import MODES from '../../../../../utils/constants/mode.js';
|
||||
import { setCourseData } from '../../../../../components/Course/store';
|
||||
import {
|
||||
setCourseData,
|
||||
course,
|
||||
} from '../../../../../components/Course/store';
|
||||
import { lesson } from '../../../../../components/Course/components/Lesson/store/lessons';
|
||||
|
||||
export let courseData;
|
||||
export let lessonData;
|
||||
export let params;
|
||||
export let lessonId;
|
||||
export let exerciseId;
|
||||
export let isMaterialsTabActive;
|
||||
|
||||
let path;
|
||||
let mode = MODES.view;
|
||||
let prevLessonId;
|
||||
|
||||
const { page } = stores();
|
||||
|
||||
function setLesson() {
|
||||
async function fetchReqData() {
|
||||
const { id: courseId, lessonParams = [] } = params;
|
||||
const [_lessonId, exerciseRouteName, _exerciseId] = lessonParams;
|
||||
|
||||
let courseData;
|
||||
let lessonData;
|
||||
|
||||
if (!$course.id) {
|
||||
const [_course, lesson] = await Promise.all([
|
||||
fetchCourse(courseId),
|
||||
fetchLesson(_lessonId),
|
||||
]);
|
||||
|
||||
courseData = _course.data;
|
||||
lessonData = lesson.data;
|
||||
} else if (prevLessonId && prevLessonId !== _lessonId) {
|
||||
const lesson = await fetchLesson(_lessonId);
|
||||
|
||||
lessonData = lesson.data;
|
||||
}
|
||||
|
||||
prevLessonId = _lessonId;
|
||||
lessonId = _lessonId;
|
||||
exerciseId = _exerciseId;
|
||||
isMaterialsTabActive = !exerciseRouteName;
|
||||
|
||||
setCourseData(courseData);
|
||||
setLesson(lessonData);
|
||||
}
|
||||
|
||||
function setLesson(lessonData) {
|
||||
if (!lessonData) return;
|
||||
|
||||
lesson.update((l) => ({
|
||||
@@ -71,19 +79,13 @@
|
||||
}));
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
setCourseData(courseData);
|
||||
|
||||
setLesson();
|
||||
});
|
||||
|
||||
afterUpdate(setLesson);
|
||||
|
||||
$: path = $page.path.replace(/\/exercises[\/ 0-9 a-z -]*/, '');
|
||||
|
||||
$: {
|
||||
if (lessonId) {
|
||||
console.log(`params`, params);
|
||||
if (params) {
|
||||
mode = MODES.view;
|
||||
fetchReqData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -119,8 +121,8 @@
|
||||
|
||||
{#if !isMaterialsTabActive}
|
||||
<Exercises {lessonId} {exerciseId} path={`${path}/exercises`} />
|
||||
{:else}
|
||||
<PageBody width="w-11/12 m-auto">
|
||||
{:else if !!lessonId}
|
||||
<PageBody>
|
||||
<Materials {lessonId} {mode} />
|
||||
</PageBody>
|
||||
{/if}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
<script context="module">
|
||||
import { fetchCourse } from '../../../../utils/services/courses';
|
||||
|
||||
export async function preload({ params }, session) {
|
||||
if (process.browser) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const {
|
||||
data,
|
||||
// error
|
||||
} = await fetchCourse(params.id, session);
|
||||
|
||||
return { courseData: data };
|
||||
export async function preload({ params }) {
|
||||
return { courseId: params.id };
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -30,6 +19,7 @@
|
||||
import TextField from '../../../../components/Form/TextField.svelte';
|
||||
import IconButton from '../../../../components/IconButton/index.svelte';
|
||||
import Select from '../../../../components/Form/Select.svelte';
|
||||
import { fetchCourse } from '../../../../utils/services/courses';
|
||||
import CourseContainer from '../../../../components/CourseContainer/index.svelte';
|
||||
import {
|
||||
lessons,
|
||||
@@ -43,7 +33,7 @@
|
||||
group,
|
||||
} from '../../../../components/Course/store';
|
||||
|
||||
export let courseData;
|
||||
export let courseId;
|
||||
|
||||
let lessonEditing;
|
||||
let ref;
|
||||
@@ -88,8 +78,10 @@
|
||||
}, 100);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
setCourseData(courseData);
|
||||
onMount(async () => {
|
||||
if ($course.id) return;
|
||||
const { data } = await fetchCourse(courseId);
|
||||
setCourseData(data);
|
||||
});
|
||||
|
||||
$: console.log(`$lessons`, $lessons);
|
||||
@@ -106,12 +98,12 @@
|
||||
</div>
|
||||
</PageNav>
|
||||
|
||||
<PageBody width="w-11/12 m-auto">
|
||||
<div class="w-4/5 mt-4 m-auto">
|
||||
<PageBody>
|
||||
<div class="w-11/12 mt-4 m-auto">
|
||||
{#each $lessons as lesson, index}
|
||||
<div
|
||||
bind:this={ref}
|
||||
class="group relative m-auto rounded-md border-2 border-gray-100 py-3 px-5 mr-4 mb-4 flex items-center hover:shadow-2xl shadow-md transition delay-150 duration-300 ease-in-out"
|
||||
class="group relative m-auto rounded-md border-2 border-gray-100 py-3 px-5 mb-4 flex items-center hover:shadow-2xl shadow-md transition delay-150 duration-300 ease-in-out"
|
||||
>
|
||||
<!-- Complete or Not complete icon -->
|
||||
<div class="absolute -left-6 -top-6 success">
|
||||
|
||||
@@ -1,71 +1,87 @@
|
||||
<script context="module">
|
||||
import { fetchCourse } from '../../../utils/services/courses';
|
||||
|
||||
export async function preload({ params }, session) {
|
||||
if (process.browser) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const {
|
||||
data,
|
||||
// error
|
||||
} = await fetchCourse(params.id, session);
|
||||
|
||||
return { courseData: data };
|
||||
export async function preload({ params }) {
|
||||
return { courseId: params.id };
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { stores } from '@sapper/app';
|
||||
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
||||
import PageNav from '../../../components/PageNav/index.svelte';
|
||||
import UserCard from '../../../components/UserCard/index.svelte';
|
||||
import { setCourseData } from '../../../components/Course/store';
|
||||
import PrimaryButton from '../../../components/PrimaryButton/index.svelte';
|
||||
import PageBody from '../../../components/PageBody/index.svelte';
|
||||
import { setCourseData, course } from '../../../components/Course/store';
|
||||
import InviationModal from '../../../components/Course/components/People/InviationModal.svelte';
|
||||
import { invitationModal } from '../../../components/Course/components/People/store';
|
||||
import type { Person } from '../../../components/Course/components/People/types';
|
||||
import { ROLE_LABEL } from '../../../utils/constants/roles';
|
||||
|
||||
export let courseData = {};
|
||||
export let courseId: string = '';
|
||||
|
||||
const { page } = stores();
|
||||
let people: Array<Person> = [];
|
||||
let borderBottomGrey = 'border-r-0 border-b border-l-0 border-gray-300';
|
||||
|
||||
const teachers = [];
|
||||
const students = [...teachers, ...teachers, ...teachers];
|
||||
function setPeople(group) {
|
||||
if (!group) return;
|
||||
const { students, tutors } = group;
|
||||
people = [...students, ...tutors].sort((a, b) => a.role_id - b.role_id);
|
||||
console.log(`group changed`, group);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
setCourseData(courseData);
|
||||
onMount(async () => {
|
||||
if ($course.id) return;
|
||||
const { data } = await fetchCourse(courseId);
|
||||
setCourseData(data);
|
||||
});
|
||||
|
||||
$: setPeople($course.group);
|
||||
</script>
|
||||
|
||||
<CourseContainer>
|
||||
<PageNav title="People" disableSticky={true} />
|
||||
<div class="m-3">
|
||||
<h3>
|
||||
<a id="teachers" name="teachers" href="{$page.path}#teachers">Teachers</a>
|
||||
</h3>
|
||||
<div class="flex flex-wrap">
|
||||
{#each teachers as teacher}
|
||||
<UserCard
|
||||
name={teacher.name}
|
||||
avatarUrl={teacher.avatar}
|
||||
title={teacher.title}
|
||||
telegramLink={teacher.telegramLink}
|
||||
mailLink={teacher.mailLink}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<InviationModal />
|
||||
|
||||
<div class="m-3">
|
||||
<h3>
|
||||
<a id="students" name="students" href="{$page.path}#students">Students</a>
|
||||
</h3>
|
||||
<div class="flex flex-wrap">
|
||||
{#each students as student}
|
||||
<UserCard
|
||||
name={student.name}
|
||||
avatarUrl={student.avatar}
|
||||
title={student.title}
|
||||
/>
|
||||
<CourseContainer>
|
||||
<PageNav title="People" disableSticky={true}>
|
||||
<slot:fragment slot="widget">
|
||||
<PrimaryButton
|
||||
className="mr-2"
|
||||
label="Add"
|
||||
onClick={() => ($invitationModal.open = true)}
|
||||
/>
|
||||
</slot:fragment>
|
||||
</PageNav>
|
||||
<PageBody className="">
|
||||
<div class="table rounded-md border border-gray-300 w-11/12 m-auto">
|
||||
<div
|
||||
class="flex items-center justify-evenly font-bold border-t-0 {borderBottomGrey} p-3"
|
||||
>
|
||||
<span class="mr-2">No</span>
|
||||
<!-- <span class="flex-grow" /> -->
|
||||
<p class="text-lg w-1/4">Name</p>
|
||||
<p class="text-lg w-1/4">Email</p>
|
||||
<p class="text-lg w-1/4 ">Role</p>
|
||||
</div>
|
||||
|
||||
{#each people as person, index}
|
||||
<div
|
||||
class="flex items-center justify-evenly p-3 cursor-pointer {borderBottomGrey} hover:bg-gray-100"
|
||||
>
|
||||
<span class="mr-2">{index + 1}</span>
|
||||
<!-- <span class="flex-grow" /> -->
|
||||
{#if person.profile}
|
||||
<p class="text-lg w-1/4 break-all">{person.profile.fullname}</p>
|
||||
{:else}
|
||||
<p
|
||||
class="bg-yellow-500 w-1/4 text-sm text-center rounded-xl text-white"
|
||||
>
|
||||
Pending confirmation
|
||||
</p>
|
||||
{/if}
|
||||
<p class="text-lg w-1/4 break-all">{person.email}</p>
|
||||
<p class="text-lg w-1/4 ">{ROLE_LABEL[person.role_id]}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</PageBody>
|
||||
</CourseContainer>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
||||
import PageNav from '../../../components/PageNav/index.svelte';
|
||||
import PageBody from '../../../components/PageBody/index.svelte';
|
||||
import { setCourseData } from '../../../components/Course/store';
|
||||
|
||||
export let courseData = {};
|
||||
@@ -30,4 +31,5 @@
|
||||
|
||||
<CourseContainer>
|
||||
<PageNav title="Scoreboard" />
|
||||
<PageBody />
|
||||
</CourseContainer>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
||||
import PageNav from '../../../components/PageNav/index.svelte';
|
||||
import PageBody from '../../../components/PageBody/index.svelte';
|
||||
import { setCourseData } from '../../../components/Course/store';
|
||||
|
||||
export let courseData = {};
|
||||
@@ -30,4 +31,5 @@
|
||||
|
||||
<CourseContainer>
|
||||
<PageNav title="Timetable" />
|
||||
<PageBody />
|
||||
</CourseContainer>
|
||||
|
||||
@@ -3,3 +3,9 @@ export const ROLE = {
|
||||
TUTOR: 2,
|
||||
STUDENT: 3,
|
||||
};
|
||||
|
||||
export const ROLE_LABEL = {
|
||||
[ROLE.ADMIN]: 'Admin',
|
||||
[ROLE.TUTOR]: 'Tutor',
|
||||
[ROLE.STUDENT]: 'Student',
|
||||
};
|
||||
|
||||
@@ -5,11 +5,17 @@ import { QUESTION_TYPE } from '../../../components/Question/constants';
|
||||
export async function fetchCourse(courseId, session) {
|
||||
let jsonUser;
|
||||
|
||||
try {
|
||||
jsonUser = JSON.parse(session.user);
|
||||
} catch (error) {
|
||||
console.log('Token is expired probably cause user not found');
|
||||
return this.redirect(301, '/login');
|
||||
if (!process.browser) {
|
||||
try {
|
||||
jsonUser = JSON.parse(session.user);
|
||||
} catch (error) {
|
||||
console.log('Token is expired probably cause user not found');
|
||||
return {
|
||||
data: [],
|
||||
cantFetch: true,
|
||||
};
|
||||
// return this.redirect(301, '/login');
|
||||
}
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
@@ -18,6 +24,7 @@ export async function fetchCourse(courseId, session) {
|
||||
`
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
overview,
|
||||
group(*,
|
||||
members:groupmember(*,
|
||||
@@ -45,6 +52,34 @@ export async function fetchCourse(courseId, session) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchGroup(groupId) {
|
||||
const { data, error } = await supabase
|
||||
.from('group')
|
||||
.select(`*,members:groupmember(*,profile(*))`)
|
||||
.match({ id: groupId })
|
||||
.single();
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export async function setProfileIdOfGroupMember(email, profileId) {
|
||||
const { data, error } = await supabase
|
||||
.from('groupmember')
|
||||
.update({
|
||||
email: null,
|
||||
profile_id: profileId,
|
||||
})
|
||||
.match({ email: email });
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateCourse(courseId, course) {
|
||||
return await supabase
|
||||
.from('course')
|
||||
@@ -52,6 +87,16 @@ export async function updateCourse(courseId, course) {
|
||||
.match({ id: courseId });
|
||||
}
|
||||
|
||||
export async function deleteCourse(courseId, course) {
|
||||
return await supabase.from('course').delete(course).match({ id: courseId });
|
||||
}
|
||||
|
||||
export function addGroupMember(member) {
|
||||
return supabase.from('groupmember').insert(member, {
|
||||
returning: 'minimal',
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchLesson(lessonId) {
|
||||
return supabase
|
||||
.from('lesson')
|
||||
@@ -117,14 +162,21 @@ export async function upsertExercise(questionnaire, exerciseId) {
|
||||
is_dirty,
|
||||
} = question;
|
||||
|
||||
// DELETE /delete/:questionId
|
||||
// DELETE /delete/:questionId - Don't delete if answer already given
|
||||
if (deleted_at) {
|
||||
// Delete from server only if this question exists in the database
|
||||
if (!isNew(id)) {
|
||||
Promise.all([
|
||||
supabase.from('option').delete().match({ question_id: id }),
|
||||
supabase.from('question').delete().match({ id }),
|
||||
]);
|
||||
const { error } = await supabase
|
||||
.from('question')
|
||||
.delete()
|
||||
.match({ id });
|
||||
|
||||
if (error) {
|
||||
console.error('Cannot delete this question', error);
|
||||
continue;
|
||||
}
|
||||
|
||||
await supabase.from('option').delete().match({ question_id: id });
|
||||
}
|
||||
|
||||
// Skip the remaining operation so we filter out this question from the new questions array
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "ES2017", "WebWorker"],
|
||||
"target": "es2017",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"importsNotUsedAsValues": "error",
|
||||
"isolatedModules": true,
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"types": ["svelte"]
|
||||
},
|
||||
"include": ["src/**/*", "src/node_modules/**/*"],
|
||||
"exclude": ["node_modules/*", "__sapper__/*", "static/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user