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
@@ -38,6 +38,7 @@
|
|||||||
"@rollup/plugin-commonjs": "^14.0.0",
|
"@rollup/plugin-commonjs": "^14.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^8.0.0",
|
"@rollup/plugin-node-resolve": "^8.0.0",
|
||||||
"@rollup/plugin-replace": "^2.4.2",
|
"@rollup/plugin-replace": "^2.4.2",
|
||||||
|
"@rollup/plugin-typescript": "^8.2.5",
|
||||||
"@tailwindcss/typography": "^0.3.1",
|
"@tailwindcss/typography": "^0.3.1",
|
||||||
"carbon-icons-svelte": "^10.21.0",
|
"carbon-icons-svelte": "^10.21.0",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
|
|||||||
+18
-4
@@ -6,6 +6,7 @@ import babel from '@rollup/plugin-babel';
|
|||||||
import { terser } from 'rollup-plugin-terser';
|
import { terser } from 'rollup-plugin-terser';
|
||||||
import config from 'sapper/config/rollup.js';
|
import config from 'sapper/config/rollup.js';
|
||||||
import sveltePreprocess from 'svelte-preprocess';
|
import sveltePreprocess from 'svelte-preprocess';
|
||||||
|
import typescript from '@rollup/plugin-typescript';
|
||||||
import json from '@rollup/plugin-json';
|
import json from '@rollup/plugin-json';
|
||||||
import sapperEnv from 'sapper-environment';
|
import sapperEnv from 'sapper-environment';
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ import pkg from './package.json';
|
|||||||
|
|
||||||
const mode = process.env.NODE_ENV;
|
const mode = process.env.NODE_ENV;
|
||||||
const dev = mode === 'development';
|
const dev = mode === 'development';
|
||||||
|
const sourcemap = dev ? 'inline' : false;
|
||||||
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
|
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
|
||||||
|
|
||||||
const onwarn = (warning, onwarn) => {
|
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 {
|
export default {
|
||||||
client: {
|
client: {
|
||||||
@@ -39,6 +41,10 @@ export default {
|
|||||||
'process.env.NODE_ENV': JSON.stringify(mode),
|
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||||
preventAssignment: true,
|
preventAssignment: true,
|
||||||
}),
|
}),
|
||||||
|
typescript({
|
||||||
|
noEmitOnError: !dev,
|
||||||
|
sourceMap: !!sourcemap,
|
||||||
|
}),
|
||||||
json(),
|
json(),
|
||||||
svelte({
|
svelte({
|
||||||
preprocess,
|
preprocess,
|
||||||
@@ -50,7 +56,9 @@ export default {
|
|||||||
browser: true,
|
browser: true,
|
||||||
dedupe: ['svelte'],
|
dedupe: ['svelte'],
|
||||||
}),
|
}),
|
||||||
commonjs(),
|
commonjs({
|
||||||
|
sourceMap: !!sourcemap,
|
||||||
|
}),
|
||||||
|
|
||||||
legacy &&
|
legacy &&
|
||||||
babel({
|
babel({
|
||||||
@@ -88,13 +96,17 @@ export default {
|
|||||||
|
|
||||||
server: {
|
server: {
|
||||||
input: config.server.input(),
|
input: config.server.input(),
|
||||||
output: config.server.output(),
|
output: { ...config.server.output(), sourcemap },
|
||||||
plugins: [
|
plugins: [
|
||||||
replace({
|
replace({
|
||||||
'process.browser': false,
|
'process.browser': false,
|
||||||
'process.env.NODE_ENV': JSON.stringify(mode),
|
'process.env.NODE_ENV': JSON.stringify(mode),
|
||||||
preventAssignment: true,
|
preventAssignment: true,
|
||||||
}),
|
}),
|
||||||
|
typescript({
|
||||||
|
noEmitOnError: !dev,
|
||||||
|
sourceMap: !!sourcemap,
|
||||||
|
}),
|
||||||
svelte({
|
svelte({
|
||||||
preprocess,
|
preprocess,
|
||||||
generate: 'ssr',
|
generate: 'ssr',
|
||||||
@@ -104,7 +116,9 @@ export default {
|
|||||||
resolve({
|
resolve({
|
||||||
dedupe: ['svelte'],
|
dedupe: ['svelte'],
|
||||||
}),
|
}),
|
||||||
commonjs(),
|
commonjs({
|
||||||
|
sourceMap: !!sourcemap,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
external: Object.keys(pkg.dependencies).concat(
|
external: Object.keys(pkg.dependencies).concat(
|
||||||
require('module').builtinModules
|
require('module').builtinModules
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getExercises() {
|
async function getExercises() {
|
||||||
|
if (!lessonId) return;
|
||||||
const exercisesData = await supabase
|
const exercisesData = await supabase
|
||||||
.from('exercise')
|
.from('exercise')
|
||||||
.select(`id, title, created_at`)
|
.select(`id, title, created_at`)
|
||||||
@@ -125,7 +126,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<PageBody width="w-11/12 m-auto">
|
<PageBody>
|
||||||
<slot:fragment slot="header">
|
<slot:fragment slot="header">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import CheckmarkFilled20 from 'carbon-icons-svelte/lib/CheckmarkFilled20';
|
import { stores, goto } from '@sapper/app';
|
||||||
import hotkeys from 'hotkeys-js';
|
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 ChevronLeft32 from 'carbon-icons-svelte/lib/ChevronLeft32';
|
||||||
import ChevronRight32 from 'carbon-icons-svelte/lib/ChevronRight32';
|
import ChevronRight32 from 'carbon-icons-svelte/lib/ChevronRight32';
|
||||||
import { stores, goto } from '@sapper/app';
|
|
||||||
import Expandable from '../../../Expandable/index.svelte';
|
import Expandable from '../../../Expandable/index.svelte';
|
||||||
import PageNav from '../../../PageNav/index.svelte';
|
import PageNav from '../../../PageNav/index.svelte';
|
||||||
import IconButton from '../../../IconButton/index.svelte';
|
import IconButton from '../../../IconButton/index.svelte';
|
||||||
@@ -14,6 +15,8 @@
|
|||||||
getLectureNo,
|
getLectureNo,
|
||||||
} from '../../function';
|
} from '../../function';
|
||||||
|
|
||||||
|
import Settings from '../Settings/index.svelte';
|
||||||
|
import { settingsDialog } from '../Settings/store';
|
||||||
import { lessons } from '../Lesson/store/lessons';
|
import { lessons } from '../Lesson/store/lessons';
|
||||||
import { course } from '../../store';
|
import { course } from '../../store';
|
||||||
import { updateCourse } from '../../../../utils/services/courses';
|
import { updateCourse } from '../../../../utils/services/courses';
|
||||||
@@ -78,32 +81,42 @@
|
|||||||
to: getNavItemRoute($course.id, 'submissions'),
|
to: getNavItemRoute($course.id, 'submissions'),
|
||||||
hideSortIcon: true,
|
hideSortIcon: true,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: 'Scoreboard',
|
// label: 'Scoreboard',
|
||||||
to: getNavItemRoute($course.id, 'scoreboard'),
|
// to: getNavItemRoute($course.id, 'scoreboard'),
|
||||||
hideSortIcon: true,
|
// hideSortIcon: true,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
label: 'People',
|
label: 'People',
|
||||||
to: getNavItemRoute($course.id, 'people'),
|
to: getNavItemRoute($course.id, 'people'),
|
||||||
hideSortIcon: true,
|
hideSortIcon: true,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: 'Timetable',
|
// label: 'Timetable',
|
||||||
to: getNavItemRoute($course.id, 'timetable'),
|
// to: getNavItemRoute($course.id, 'timetable'),
|
||||||
hideSortIcon: true,
|
// hideSortIcon: true,
|
||||||
},
|
// },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<Settings />
|
||||||
<div class="root z-10 {!show && 'hide'}">
|
<div class="root z-10 {!show && 'hide'}">
|
||||||
{#if show}
|
{#if show}
|
||||||
<PageNav
|
<PageNav
|
||||||
bind:title={$course.title}
|
bind:title={$course.title}
|
||||||
onEditComplete={handleSaveTitle}
|
onEditComplete={handleSaveTitle}
|
||||||
isTitleEditable={true}
|
isTitleEditable={true}
|
||||||
/>
|
paddingClass="pl-2"
|
||||||
|
>
|
||||||
|
<slot:fragment slot="widget">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => ($settingsDialog.open = !$settingsDialog.open)}
|
||||||
|
>
|
||||||
|
<Settings20 class="carbon-icon" />
|
||||||
|
</IconButton>
|
||||||
|
</slot:fragment>
|
||||||
|
</PageNav>
|
||||||
<div>
|
<div>
|
||||||
{#each navItems as navItem}
|
{#each navItems as navItem}
|
||||||
<Expandable
|
<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 PageNav from '../PageNav/index.svelte';
|
||||||
import MODES from '../../utils/constants/mode';
|
import MODES from '../../utils/constants/mode';
|
||||||
import PrimaryButton from '../PrimaryButton/index.svelte';
|
import PrimaryButton from '../PrimaryButton/index.svelte';
|
||||||
|
import PageBody from '../PageBody/index.svelte';
|
||||||
import EditContent from '../EditContent/index.svelte';
|
import EditContent from '../EditContent/index.svelte';
|
||||||
import { course } from '../Course/store';
|
import { course } from '../Course/store';
|
||||||
import { updateCourse } from '../../utils/services/courses';
|
import { updateCourse } from '../../utils/services/courses';
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</PageNav>
|
</PageNav>
|
||||||
|
|
||||||
<div class="px-3">
|
<PageBody>
|
||||||
{#if mode === MODES.edit}
|
{#if mode === MODES.edit}
|
||||||
<EditContent
|
<EditContent
|
||||||
writeLabel="Note"
|
writeLabel="Note"
|
||||||
@@ -51,4 +52,4 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{@html marked($course.overview || '', { breaks: true, gfm: true })}
|
{@html marked($course.overview || '', { breaks: true, gfm: true })}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</PageBody>
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ export const group = writable({
|
|||||||
students: [],
|
students: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function setCourseData(data) {
|
export async function setCourseData(data, setLesson = true) {
|
||||||
if (!data || !(Object.values(data) && Object.values(data).length)) return;
|
if (!data || !(Object.values(data) && Object.values(data).length)) return;
|
||||||
console.log(`data`, data);
|
console.log(`setCourseData`, data);
|
||||||
|
|
||||||
if (data.group) {
|
if (data.group) {
|
||||||
const groupData = Object.assign(data.group, { tutors: [], students: [] });
|
const groupData = Object.assign(data.group, { tutors: [], students: [] });
|
||||||
@@ -37,7 +37,8 @@ export async function setCourseData(data) {
|
|||||||
for (const member of groupData.members) {
|
for (const member of groupData.members) {
|
||||||
if (member.role_id === ROLE.STUDENT) {
|
if (member.role_id === ROLE.STUDENT) {
|
||||||
groupData.students.push(member);
|
groupData.students.push(member);
|
||||||
} else if (member.profile) {
|
// } else if (member.profile) {
|
||||||
|
} else {
|
||||||
groupData.tutors.push({
|
groupData.tutors.push({
|
||||||
...member,
|
...member,
|
||||||
...member.profile,
|
...member.profile,
|
||||||
@@ -51,12 +52,13 @@ export async function setCourseData(data) {
|
|||||||
group.set(groupData);
|
group.set(groupData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderedLessons = (data.lessons || []).sort(
|
if (setLesson) {
|
||||||
(a, b) => new Date(a.created_at) - new Date(b.created_at)
|
const orderedLessons = (data.lessons || []).sort(
|
||||||
);
|
(a, b) => new Date(a.created_at) - new Date(b.created_at)
|
||||||
// .map();
|
);
|
||||||
|
// .map();
|
||||||
lessons.set(orderedLessons);
|
lessons.set(orderedLessons);
|
||||||
|
}
|
||||||
|
|
||||||
delete data.lessons;
|
delete data.lessons;
|
||||||
course.set(data);
|
course.set(data);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import { course } from '../Course/store';
|
import { course } from '../Course/store';
|
||||||
import Confetti from '../Confetti/index.svelte';
|
import Confetti from '../Confetti/index.svelte';
|
||||||
|
|
||||||
export let path;
|
export let path = '';
|
||||||
export let isExercisePage = false;
|
export let isExercisePage = false;
|
||||||
|
|
||||||
function setStoreWithData() {
|
function setStoreWithData() {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
// import
|
// import
|
||||||
// border border-gray-300 rounded-md p-3 bg-blue-200 bg-opacity-25
|
// 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>
|
</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}
|
{#if $$slots.header}
|
||||||
<div
|
<div
|
||||||
class="head flex bg-white items-center justify-between sticky right-0 w-full py-3"
|
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 title = '';
|
||||||
export let overidableStyle = '';
|
export let overidableStyle = '';
|
||||||
export let navItems = [];
|
export let navItems = [];
|
||||||
export let addButtonHref;
|
export let addButtonHref = '';
|
||||||
export let addButtonLabel;
|
export let addButtonLabel = '';
|
||||||
export let disableSticky = false;
|
export let disableSticky = false;
|
||||||
export let isTitleEditable = false;
|
export let isTitleEditable = false;
|
||||||
export let onEditComplete = () => {};
|
export let onEditComplete = () => {};
|
||||||
|
export let paddingClass = 'px-2';
|
||||||
|
|
||||||
let dynamicRootClass = '';
|
let dynamicRootClass = '';
|
||||||
let enterEditTitleMode = false;
|
let enterEditTitleMode = false;
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="header flex items-center justify-between {!disableSticky &&
|
class="header flex items-center justify-between {!disableSticky &&
|
||||||
'sticky'} {dynamicRootClass}"
|
'sticky'} {paddingClass} {dynamicRootClass}"
|
||||||
style={overidableStyle}
|
style={overidableStyle}
|
||||||
>
|
>
|
||||||
{#if !!title}
|
{#if !!title}
|
||||||
@@ -66,7 +67,6 @@
|
|||||||
<style>
|
<style>
|
||||||
.header {
|
.header {
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
padding: 0 15px;
|
|
||||||
min-height: 61px;
|
min-height: 61px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
export const VARIANTS = {
|
export const VARIANTS = {
|
||||||
CONTAINED: "CONTAINED",
|
CONTAINED: 'CONTAINED',
|
||||||
OUTLINED: "OUTLINED",
|
CONTAINED_DANGER: 'CONTAINED_DANGER',
|
||||||
|
OUTLINED: 'OUTLINED',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VARIANTS_CLASS = {
|
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]:
|
[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 { handleAuthChange } from '../utils/functions/api';
|
||||||
import { user, profile } from '../utils/store/user';
|
import { user, profile } from '../utils/store/user';
|
||||||
import { getSupabase } from '../utils/functions/supabase';
|
import { getSupabase } from '../utils/functions/supabase';
|
||||||
|
import { setProfileIdOfGroupMember } from '../utils/services/courses';
|
||||||
|
|
||||||
export let segment;
|
export let segment;
|
||||||
export let config;
|
export let config;
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
id: authUser.id,
|
id: authUser.id,
|
||||||
username: regexUsernameMatch[1],
|
username: regexUsernameMatch[1],
|
||||||
fullname: regexUsernameMatch[1],
|
fullname: regexUsernameMatch[1],
|
||||||
|
email: authUser.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Profile created, go to profile page
|
// Profile created, go to profile page
|
||||||
@@ -61,6 +63,7 @@
|
|||||||
$user.currentSession = authUser;
|
$user.currentSession = authUser;
|
||||||
|
|
||||||
profile.set(data[0]);
|
profile.set(data[0]);
|
||||||
|
await setProfileIdOfGroupMember(authUser.email, data[0].id);
|
||||||
|
|
||||||
// If user coming to login page, then
|
// If user coming to login page, then
|
||||||
if (path.includes('login') || path.includes('signup')) {
|
if (path.includes('login') || path.includes('signup')) {
|
||||||
@@ -75,6 +78,8 @@
|
|||||||
console.log(`profileData`, profileData);
|
console.log(`profileData`, profileData);
|
||||||
profile.set(profileData);
|
profile.set(profileData);
|
||||||
|
|
||||||
|
setProfileIdOfGroupMember(authUser.email, profileData.id);
|
||||||
|
|
||||||
// If user coming to login page, then
|
// If user coming to login page, then
|
||||||
if (path.includes('login') || path.includes('signup')) {
|
if (path.includes('login') || path.includes('signup')) {
|
||||||
goto('/dashboard');
|
goto('/dashboard');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script context="module">
|
<script context="module">
|
||||||
import { profile, user } from '../utils/store/user';
|
import { profile } from '../utils/store/user';
|
||||||
import { fetchCourses } from '../components/Courses/api';
|
import { fetchCourses } from '../components/Courses/api';
|
||||||
|
|
||||||
export async function preload(page, params) {
|
export async function preload(page, params) {
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
import { courses, createCourseModal } from '../components/Courses/store';
|
import { courses, createCourseModal } from '../components/Courses/store';
|
||||||
import { validateForm } from '../components/Courses/functions';
|
import { validateForm } from '../components/Courses/functions';
|
||||||
import { ROLE } from '../utils/constants/roles';
|
import { ROLE } from '../utils/constants/roles';
|
||||||
|
import { addGroupMember } from '../utils/services/courses';
|
||||||
|
|
||||||
// const { page } = stores();
|
// const { page } = stores();
|
||||||
|
|
||||||
@@ -69,8 +70,9 @@
|
|||||||
// Get profile of author and add as member
|
// Get profile of author and add as member
|
||||||
const admin = await addGroupMember({
|
const admin = await addGroupMember({
|
||||||
profile_id: $profile.id,
|
profile_id: $profile.id,
|
||||||
|
email: student,
|
||||||
group_id,
|
group_id,
|
||||||
role_id: ROLE.ADMIN,
|
role_id: ROLE.TUTOR,
|
||||||
});
|
});
|
||||||
|
|
||||||
let membersStack = [];
|
let membersStack = [];
|
||||||
@@ -97,12 +99,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addGroupMember(member) {
|
|
||||||
return supabase.from('groupmember').insert(member, {
|
|
||||||
returning: 'minimal',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getCourses(userId) {
|
async function getCourses(userId) {
|
||||||
console.log(`getCourses process.browser`, process.browser);
|
console.log(`getCourses process.browser`, process.browser);
|
||||||
if (process.browser) {
|
if (process.browser) {
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
<script context="module">
|
<script context="module">
|
||||||
import { fetchCourse } from '../../../utils/services/courses';
|
|
||||||
|
|
||||||
export async function preload({ params }, session) {
|
export async function preload({ params }, session) {
|
||||||
const {
|
return { courseId: params.id };
|
||||||
data,
|
|
||||||
// error
|
|
||||||
} = await fetchCourse(params.id, session);
|
|
||||||
|
|
||||||
return { courseData: data };
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -15,12 +8,17 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Overview from '../../../components/Course/index.svelte';
|
import Overview from '../../../components/Course/index.svelte';
|
||||||
import CourseContainer from '../../../components/CourseContainer/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 () => {
|
onMount(async () => {
|
||||||
setCourseData(courseData);
|
console.log(`overview courseId`, courseId);
|
||||||
|
if ($course.id) return;
|
||||||
|
|
||||||
|
const { data } = await fetchCourse(courseId);
|
||||||
|
setCourseData(data);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,35 +3,10 @@
|
|||||||
fetchCourse,
|
fetchCourse,
|
||||||
fetchLesson,
|
fetchLesson,
|
||||||
} from '../../../../../utils/services/courses';
|
} from '../../../../../utils/services/courses';
|
||||||
let prevLessonId;
|
|
||||||
|
|
||||||
export async function preload({ params }, session) {
|
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 {
|
return {
|
||||||
courseData,
|
params,
|
||||||
lessonData,
|
|
||||||
isMaterialsTabActive: !exerciseRouteName,
|
|
||||||
lessonId,
|
|
||||||
exerciseId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -47,21 +22,54 @@
|
|||||||
import Materials from '../../../../../components/Course/components/Lesson/Materials/index.svelte';
|
import Materials from '../../../../../components/Course/components/Lesson/Materials/index.svelte';
|
||||||
import Exercises from '../../../../../components/Course/components/Lesson/Exercises/index.svelte';
|
import Exercises from '../../../../../components/Course/components/Lesson/Exercises/index.svelte';
|
||||||
import MODES from '../../../../../utils/constants/mode.js';
|
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';
|
import { lesson } from '../../../../../components/Course/components/Lesson/store/lessons';
|
||||||
|
|
||||||
export let courseData;
|
export let params;
|
||||||
export let lessonData;
|
|
||||||
export let lessonId;
|
export let lessonId;
|
||||||
export let exerciseId;
|
export let exerciseId;
|
||||||
export let isMaterialsTabActive;
|
export let isMaterialsTabActive;
|
||||||
|
|
||||||
let path;
|
let path;
|
||||||
let mode = MODES.view;
|
let mode = MODES.view;
|
||||||
|
let prevLessonId;
|
||||||
|
|
||||||
const { page } = stores();
|
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;
|
if (!lessonData) return;
|
||||||
|
|
||||||
lesson.update((l) => ({
|
lesson.update((l) => ({
|
||||||
@@ -71,19 +79,13 @@
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
setCourseData(courseData);
|
|
||||||
|
|
||||||
setLesson();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterUpdate(setLesson);
|
|
||||||
|
|
||||||
$: path = $page.path.replace(/\/exercises[\/ 0-9 a-z -]*/, '');
|
$: path = $page.path.replace(/\/exercises[\/ 0-9 a-z -]*/, '');
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (lessonId) {
|
console.log(`params`, params);
|
||||||
|
if (params) {
|
||||||
mode = MODES.view;
|
mode = MODES.view;
|
||||||
|
fetchReqData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -119,8 +121,8 @@
|
|||||||
|
|
||||||
{#if !isMaterialsTabActive}
|
{#if !isMaterialsTabActive}
|
||||||
<Exercises {lessonId} {exerciseId} path={`${path}/exercises`} />
|
<Exercises {lessonId} {exerciseId} path={`${path}/exercises`} />
|
||||||
{:else}
|
{:else if !!lessonId}
|
||||||
<PageBody width="w-11/12 m-auto">
|
<PageBody>
|
||||||
<Materials {lessonId} {mode} />
|
<Materials {lessonId} {mode} />
|
||||||
</PageBody>
|
</PageBody>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,17 +1,6 @@
|
|||||||
<script context="module">
|
<script context="module">
|
||||||
import { fetchCourse } from '../../../../utils/services/courses';
|
export async function preload({ params }) {
|
||||||
|
return { courseId: params.id };
|
||||||
export async function preload({ params }, session) {
|
|
||||||
if (process.browser) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
// error
|
|
||||||
} = await fetchCourse(params.id, session);
|
|
||||||
|
|
||||||
return { courseData: data };
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -30,6 +19,7 @@
|
|||||||
import TextField from '../../../../components/Form/TextField.svelte';
|
import TextField from '../../../../components/Form/TextField.svelte';
|
||||||
import IconButton from '../../../../components/IconButton/index.svelte';
|
import IconButton from '../../../../components/IconButton/index.svelte';
|
||||||
import Select from '../../../../components/Form/Select.svelte';
|
import Select from '../../../../components/Form/Select.svelte';
|
||||||
|
import { fetchCourse } from '../../../../utils/services/courses';
|
||||||
import CourseContainer from '../../../../components/CourseContainer/index.svelte';
|
import CourseContainer from '../../../../components/CourseContainer/index.svelte';
|
||||||
import {
|
import {
|
||||||
lessons,
|
lessons,
|
||||||
@@ -43,7 +33,7 @@
|
|||||||
group,
|
group,
|
||||||
} from '../../../../components/Course/store';
|
} from '../../../../components/Course/store';
|
||||||
|
|
||||||
export let courseData;
|
export let courseId;
|
||||||
|
|
||||||
let lessonEditing;
|
let lessonEditing;
|
||||||
let ref;
|
let ref;
|
||||||
@@ -88,8 +78,10 @@
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
setCourseData(courseData);
|
if ($course.id) return;
|
||||||
|
const { data } = await fetchCourse(courseId);
|
||||||
|
setCourseData(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
$: console.log(`$lessons`, $lessons);
|
$: console.log(`$lessons`, $lessons);
|
||||||
@@ -106,12 +98,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</PageNav>
|
</PageNav>
|
||||||
|
|
||||||
<PageBody width="w-11/12 m-auto">
|
<PageBody>
|
||||||
<div class="w-4/5 mt-4 m-auto">
|
<div class="w-11/12 mt-4 m-auto">
|
||||||
{#each $lessons as lesson, index}
|
{#each $lessons as lesson, index}
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
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 -->
|
<!-- Complete or Not complete icon -->
|
||||||
<div class="absolute -left-6 -top-6 success">
|
<div class="absolute -left-6 -top-6 success">
|
||||||
|
|||||||
@@ -1,71 +1,87 @@
|
|||||||
<script context="module">
|
<script context="module">
|
||||||
import { fetchCourse } from '../../../utils/services/courses';
|
import { fetchCourse } from '../../../utils/services/courses';
|
||||||
|
|
||||||
export async function preload({ params }, session) {
|
export async function preload({ params }) {
|
||||||
if (process.browser) {
|
return { courseId: params.id };
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
// error
|
|
||||||
} = await fetchCourse(params.id, session);
|
|
||||||
|
|
||||||
return { courseData: data };
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { stores } from '@sapper/app';
|
|
||||||
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
||||||
import PageNav from '../../../components/PageNav/index.svelte';
|
import PageNav from '../../../components/PageNav/index.svelte';
|
||||||
import UserCard from '../../../components/UserCard/index.svelte';
|
import PrimaryButton from '../../../components/PrimaryButton/index.svelte';
|
||||||
import { setCourseData } from '../../../components/Course/store';
|
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 = [];
|
function setPeople(group) {
|
||||||
const students = [...teachers, ...teachers, ...teachers];
|
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(() => {
|
onMount(async () => {
|
||||||
setCourseData(courseData);
|
if ($course.id) return;
|
||||||
|
const { data } = await fetchCourse(courseId);
|
||||||
|
setCourseData(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$: setPeople($course.group);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CourseContainer>
|
<InviationModal />
|
||||||
<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>
|
|
||||||
|
|
||||||
<div class="m-3">
|
<CourseContainer>
|
||||||
<h3>
|
<PageNav title="People" disableSticky={true}>
|
||||||
<a id="students" name="students" href="{$page.path}#students">Students</a>
|
<slot:fragment slot="widget">
|
||||||
</h3>
|
<PrimaryButton
|
||||||
<div class="flex flex-wrap">
|
className="mr-2"
|
||||||
{#each students as student}
|
label="Add"
|
||||||
<UserCard
|
onClick={() => ($invitationModal.open = true)}
|
||||||
name={student.name}
|
/>
|
||||||
avatarUrl={student.avatar}
|
</slot:fragment>
|
||||||
title={student.title}
|
</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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PageBody>
|
||||||
</CourseContainer>
|
</CourseContainer>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
||||||
import PageNav from '../../../components/PageNav/index.svelte';
|
import PageNav from '../../../components/PageNav/index.svelte';
|
||||||
|
import PageBody from '../../../components/PageBody/index.svelte';
|
||||||
import { setCourseData } from '../../../components/Course/store';
|
import { setCourseData } from '../../../components/Course/store';
|
||||||
|
|
||||||
export let courseData = {};
|
export let courseData = {};
|
||||||
@@ -30,4 +31,5 @@
|
|||||||
|
|
||||||
<CourseContainer>
|
<CourseContainer>
|
||||||
<PageNav title="Scoreboard" />
|
<PageNav title="Scoreboard" />
|
||||||
|
<PageBody />
|
||||||
</CourseContainer>
|
</CourseContainer>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
import CourseContainer from '../../../components/CourseContainer/index.svelte';
|
||||||
import PageNav from '../../../components/PageNav/index.svelte';
|
import PageNav from '../../../components/PageNav/index.svelte';
|
||||||
|
import PageBody from '../../../components/PageBody/index.svelte';
|
||||||
import { setCourseData } from '../../../components/Course/store';
|
import { setCourseData } from '../../../components/Course/store';
|
||||||
|
|
||||||
export let courseData = {};
|
export let courseData = {};
|
||||||
@@ -30,4 +31,5 @@
|
|||||||
|
|
||||||
<CourseContainer>
|
<CourseContainer>
|
||||||
<PageNav title="Timetable" />
|
<PageNav title="Timetable" />
|
||||||
|
<PageBody />
|
||||||
</CourseContainer>
|
</CourseContainer>
|
||||||
|
|||||||
@@ -3,3 +3,9 @@ export const ROLE = {
|
|||||||
TUTOR: 2,
|
TUTOR: 2,
|
||||||
STUDENT: 3,
|
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) {
|
export async function fetchCourse(courseId, session) {
|
||||||
let jsonUser;
|
let jsonUser;
|
||||||
|
|
||||||
try {
|
if (!process.browser) {
|
||||||
jsonUser = JSON.parse(session.user);
|
try {
|
||||||
} catch (error) {
|
jsonUser = JSON.parse(session.user);
|
||||||
console.log('Token is expired probably cause user not found');
|
} catch (error) {
|
||||||
return this.redirect(301, '/login');
|
console.log('Token is expired probably cause user not found');
|
||||||
|
return {
|
||||||
|
data: [],
|
||||||
|
cantFetch: true,
|
||||||
|
};
|
||||||
|
// return this.redirect(301, '/login');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
@@ -18,6 +24,7 @@ export async function fetchCourse(courseId, session) {
|
|||||||
`
|
`
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
|
description,
|
||||||
overview,
|
overview,
|
||||||
group(*,
|
group(*,
|
||||||
members:groupmember(*,
|
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) {
|
export async function updateCourse(courseId, course) {
|
||||||
return await supabase
|
return await supabase
|
||||||
.from('course')
|
.from('course')
|
||||||
@@ -52,6 +87,16 @@ export async function updateCourse(courseId, course) {
|
|||||||
.match({ id: courseId });
|
.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) {
|
export function fetchLesson(lessonId) {
|
||||||
return supabase
|
return supabase
|
||||||
.from('lesson')
|
.from('lesson')
|
||||||
@@ -117,14 +162,21 @@ export async function upsertExercise(questionnaire, exerciseId) {
|
|||||||
is_dirty,
|
is_dirty,
|
||||||
} = question;
|
} = question;
|
||||||
|
|
||||||
// DELETE /delete/:questionId
|
// DELETE /delete/:questionId - Don't delete if answer already given
|
||||||
if (deleted_at) {
|
if (deleted_at) {
|
||||||
// Delete from server only if this question exists in the database
|
// Delete from server only if this question exists in the database
|
||||||
if (!isNew(id)) {
|
if (!isNew(id)) {
|
||||||
Promise.all([
|
const { error } = await supabase
|
||||||
supabase.from('option').delete().match({ question_id: id }),
|
.from('question')
|
||||||
supabase.from('question').delete().match({ id }),
|
.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
|
// 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