Make lesson draggable

This commit is contained in:
rotimi-best
2021-09-14 07:01:44 +03:00
parent ef08b48a6e
commit 8c14b348f7
4 changed files with 76 additions and 22 deletions
+3 -3
View File
@@ -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 -1
View File
@@ -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"
+70 -17
View File
@@ -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;
+1 -1
View File
@@ -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(*)