Make lesson draggable
This commit is contained in:
@@ -57,9 +57,9 @@ export async function setCourseData(data, setLesson = true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (setLesson) {
|
if (setLesson) {
|
||||||
const orderedLessons = (data.lessons || []).sort(
|
const orderedLessons = (data.lessons || [])
|
||||||
(a, b) => new Date(a.created_at) - new Date(b.created_at)
|
.sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
|
||||||
);
|
.sort((a, b) => a.order - b.order);
|
||||||
// .map((lesson) => ({
|
// .map((lesson) => ({
|
||||||
// ...lesson,
|
// ...lesson,
|
||||||
// profile: lesson.profile && tutorsById[lesson.profile.id],
|
// profile: lesson.profile && tutorsById[lesson.profile.id],
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
// 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 w-11/12';
|
export let width = 'max-w-3xl w-11/12';
|
||||||
|
export let padding = 'pb-5 px-4';
|
||||||
export let className = '';
|
export let className = '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root mt-4 mx-auto {width} {className} relative pb-5 px-4">
|
<div class="root mt-4 mx-auto {width} {className} relative {padding}">
|
||||||
{#if $$slots.header}
|
{#if $$slots.header}
|
||||||
<div
|
<div
|
||||||
class="head flex items-center justify-between sticky right-0 w-full py-3"
|
class="head flex items-center justify-between sticky right-0 w-full py-3"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { stores } from '@sapper/app';
|
import { stores } from '@sapper/app';
|
||||||
|
import { dndzone } from 'svelte-dnd-action';
|
||||||
import Time24 from 'carbon-icons-svelte/lib/Time24';
|
import Time24 from 'carbon-icons-svelte/lib/Time24';
|
||||||
import Edit24 from 'carbon-icons-svelte/lib/Edit24';
|
import Edit24 from 'carbon-icons-svelte/lib/Edit24';
|
||||||
import Save24 from 'carbon-icons-svelte/lib/Save24';
|
import Save24 from 'carbon-icons-svelte/lib/Save24';
|
||||||
@@ -21,7 +22,10 @@
|
|||||||
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 {
|
||||||
|
fetchCourse,
|
||||||
|
updateLesson,
|
||||||
|
} from '../../../../utils/services/courses';
|
||||||
import CourseContainer from '../../../../components/CourseContainer/index.svelte';
|
import CourseContainer from '../../../../components/CourseContainer/index.svelte';
|
||||||
import { profile } from '../../../../utils/store/user';
|
import { profile } from '../../../../utils/store/user';
|
||||||
import {
|
import {
|
||||||
@@ -43,6 +47,7 @@
|
|||||||
let isStudent = true;
|
let isStudent = true;
|
||||||
|
|
||||||
const { page } = stores();
|
const { page } = stores();
|
||||||
|
const flipDurationMs = 300;
|
||||||
|
|
||||||
function formatDate(date) {
|
function formatDate(date) {
|
||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
@@ -69,6 +74,36 @@
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleDndConsider(e) {
|
||||||
|
$lessons = e.detail.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDndFinalize(e) {
|
||||||
|
const prevLessonsByOrder = $lessons.reduce(
|
||||||
|
(acc, l) => ({ ...acc, [l.id]: l.order }),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
$lessons = e.detail.items;
|
||||||
|
|
||||||
|
// Only update the lesson order that changed
|
||||||
|
e.detail.items.forEach((item, index) => {
|
||||||
|
const order = index + 1;
|
||||||
|
|
||||||
|
if (order !== prevLessonsByOrder[item.id]) {
|
||||||
|
$lessons[index].order = order;
|
||||||
|
updateLesson({ order }, item.id).then((update) =>
|
||||||
|
console.log(`updated lesson order`, update)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLessonOrder(id) {
|
||||||
|
const index = $lessons.findIndex((lesson) => lesson.id === id);
|
||||||
|
|
||||||
|
return index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if ($course.id) return;
|
if ($course.id) return;
|
||||||
const { data } = await fetchCourse(courseId);
|
const { data } = await fetchCourse(courseId);
|
||||||
@@ -88,23 +123,35 @@
|
|||||||
|
|
||||||
<CourseContainer>
|
<CourseContainer>
|
||||||
<PageNav title="Lessons">
|
<PageNav title="Lessons">
|
||||||
<RoleBasedSecurity allowedRoles="[1,2]">
|
<div slot="widget">
|
||||||
<div slot="widget">
|
<RoleBasedSecurity allowedRoles="[1,2]">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
label="Add"
|
label="Add"
|
||||||
onClick={addLesson}
|
onClick={addLesson}
|
||||||
isDisabled={!!lessonEditing}
|
isDisabled={!!lessonEditing}
|
||||||
/>
|
/>
|
||||||
</div>
|
</RoleBasedSecurity>
|
||||||
</RoleBasedSecurity>
|
</div>
|
||||||
</PageNav>
|
</PageNav>
|
||||||
|
|
||||||
<PageBody>
|
<PageBody padding="p-0">
|
||||||
<div class="w-11/12 mt-4 m-auto">
|
<section
|
||||||
{#each $lessons as lesson, index}
|
class="w-11/12 py-3 px-4 m-auto lesson-list"
|
||||||
|
use:dndzone={{
|
||||||
|
items: $lessons,
|
||||||
|
flipDurationMs,
|
||||||
|
dropTargetStyle: {
|
||||||
|
border: '2px #1d4ed8 solid',
|
||||||
|
'border-style': 'dashed',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
on:consider={handleDndConsider}
|
||||||
|
on:finalize={handleDndFinalize}
|
||||||
|
>
|
||||||
|
{#each $lessons as lesson (lesson.id)}
|
||||||
<div
|
<div
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
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"
|
class="group cursor-move 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">
|
||||||
@@ -132,7 +179,7 @@
|
|||||||
<RoleBasedSecurity allowedRoles="[1,2]">
|
<RoleBasedSecurity allowedRoles="[1,2]">
|
||||||
<!-- Edit/Save -->
|
<!-- Edit/Save -->
|
||||||
<div class="absolute top-2 right-0">
|
<div class="absolute top-2 right-0">
|
||||||
{#if lessonEditing === index}
|
{#if lessonEditing === lesson.id}
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
lessonEditing = null;
|
lessonEditing = null;
|
||||||
@@ -142,7 +189,7 @@
|
|||||||
<Save24 class="carbon-icon" />
|
<Save24 class="carbon-icon" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{:else}
|
{:else}
|
||||||
<IconButton onClick={() => (lessonEditing = index)}>
|
<IconButton onClick={() => (lessonEditing = lesson.id)}>
|
||||||
<Edit24 class="carbon-icon" />
|
<Edit24 class="carbon-icon" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -150,10 +197,12 @@
|
|||||||
</RoleBasedSecurity>
|
</RoleBasedSecurity>
|
||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
<h3 class="text-3xl font-bold">{index + 1}.</h3>
|
<h3 class="text-3xl font-bold">
|
||||||
|
{getLessonOrder(lesson.id)}.
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-8 w-4/5">
|
<div class="ml-8 w-4/5">
|
||||||
{#if lessonEditing === index}
|
{#if lessonEditing === lesson.id}
|
||||||
<TextField bind:value={lesson.title} autofocus={true} />
|
<TextField bind:value={lesson.title} autofocus={true} />
|
||||||
{:else}
|
{:else}
|
||||||
<h3 class="text-2xl m-0 flex items-center">
|
<h3 class="text-2xl m-0 flex items-center">
|
||||||
@@ -177,7 +226,7 @@
|
|||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<div class="flex items-center justify-between mt-6 w-full">
|
<div class="flex items-center justify-between mt-6 w-full">
|
||||||
{#if lessonEditing === index}
|
{#if lessonEditing === lesson.id}
|
||||||
<Select
|
<Select
|
||||||
bind:value={lesson.profile}
|
bind:value={lesson.profile}
|
||||||
options={$group.tutors}
|
options={$group.tutors}
|
||||||
@@ -201,7 +250,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="flex items-cente">
|
<div class="flex items-cente">
|
||||||
{#if lessonEditing === index}
|
{#if lessonEditing === lesson.id}
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
name="lesson-date-picker"
|
name="lesson-date-picker"
|
||||||
@@ -218,7 +267,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
{#if lessonEditing === index}
|
{#if lessonEditing === lesson.id}
|
||||||
<TextField
|
<TextField
|
||||||
bind:value={lesson.call_url}
|
bind:value={lesson.call_url}
|
||||||
autofocus={true}
|
autofocus={true}
|
||||||
@@ -253,11 +302,15 @@
|
|||||||
<img alt="No lesson added" src="/notfound2.gif" class="w-80" />
|
<img alt="No lesson added" src="/notfound2.gif" class="w-80" />
|
||||||
</Box>
|
</Box>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</section>
|
||||||
</PageBody>
|
</PageBody>
|
||||||
</CourseContainer>
|
</CourseContainer>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.lesson-list {
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 84vh;
|
||||||
|
}
|
||||||
.group {
|
.group {
|
||||||
min-height: 170px;
|
min-height: 170px;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export async function fetchCourse(courseId, session) {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
lessons:lesson(
|
lessons:lesson(
|
||||||
id, title,public, lesson_at, call_url, is_complete,created_at,
|
id, title,public, lesson_at, call_url, is_complete, order, created_at,
|
||||||
profile:teacher_id(id, avatar_url, fullname)
|
profile:teacher_id(id, avatar_url, fullname)
|
||||||
),
|
),
|
||||||
attendance:group_attendance(*)
|
attendance:group_attendance(*)
|
||||||
|
|||||||
Reference in New Issue
Block a user