mirror of
https://github.com/frappe/lms.git
synced 2026-05-02 13:39:31 +03:00
feat: see all upcoming slots when scheduling evaluation
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-if="user.data?.is_student">
|
||||
<div>
|
||||
<div class="leading-5 mb-4">
|
||||
<div class="leading-5 mb-4 text-ink-gray-7">
|
||||
<div v-if="readOnly">
|
||||
{{ __('Thank you for providing your feedback.') }}
|
||||
<span
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Dialog
|
||||
v-model="show"
|
||||
:options="{
|
||||
title: __('Schedule Evaluation'),
|
||||
title: __('Schedule your evaluation'),
|
||||
size: 'xl',
|
||||
actions: [
|
||||
{
|
||||
@@ -14,52 +14,49 @@
|
||||
}"
|
||||
>
|
||||
<template #body-content>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<div class="mb-1.5 text-sm text-ink-gray-5">
|
||||
{{ __('Course') }}
|
||||
<div class="flex flex-col gap-4 text-base max-h-[60vh]">
|
||||
<FormControl
|
||||
v-model="evaluation.course"
|
||||
type="select"
|
||||
:label="__('Course')"
|
||||
:options="getCourses()"
|
||||
/>
|
||||
<div v-if="slots.data?.length" class="space-y-4 overflow-y-auto mt-4">
|
||||
<div class="text-ink-gray-9 font-medium">
|
||||
{{ __('Available Slots') }}
|
||||
</div>
|
||||
<Select v-model="evaluation.course" :options="getCourses()" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1.5 text-sm text-ink-gray-5">
|
||||
{{ __('Date') }}
|
||||
</div>
|
||||
<FormControl
|
||||
type="date"
|
||||
v-model="evaluation.date"
|
||||
:min="
|
||||
dayjs()
|
||||
.add(dayjs.duration({ days: 1 }))
|
||||
.format('YYYY-MM-DD')
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="slots.data?.length">
|
||||
<div class="mb-1.5 text-sm text-ink-gray-5">
|
||||
{{ __('Select a slot') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div v-for="slot in slots.data">
|
||||
<div
|
||||
class="text-base text-center border rounded-md text-ink-gray-8 bg-surface-gray-3 p-2 cursor-pointer"
|
||||
@click="saveSlot(slot)"
|
||||
:class="{
|
||||
'border-outline-gray-4':
|
||||
evaluation.start_time == slot.start_time,
|
||||
}"
|
||||
>
|
||||
{{ formatTime(slot.start_time) }} -
|
||||
{{ formatTime(slot.end_time) }}
|
||||
<div class="space-y-5">
|
||||
<div v-for="row in slots.data" class="space-y-2">
|
||||
<div class="flex items-center text-ink-gray-7 space-x-2">
|
||||
<Calendar class="size-3" />
|
||||
<div class="">
|
||||
{{ dayjs(row.date).format('DD MMMM YYYY') }}
|
||||
</div>
|
||||
<div>·</div>
|
||||
<div class="text-ink-gray-5">
|
||||
{{ row.day }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div
|
||||
v-for="slot in row.slots"
|
||||
class="text-base text-center border rounded-md text-ink-gray-8 p-2 cursor-pointer text-ink-gray-7 hover:bg-surface-gray-2 hover:border-outline-gray-3"
|
||||
@click="saveSlot(slot, row)"
|
||||
:class="{
|
||||
'border-outline-gray-4 text-ink-gray-9':
|
||||
evaluation.date == row.date &&
|
||||
evaluation.start_time == slot.start_time,
|
||||
}"
|
||||
>
|
||||
{{ formatTime(slot.start_time) }} -
|
||||
{{ formatTime(slot.end_time) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="evaluation.course && evaluation.date"
|
||||
class="text-sm italic text-ink-red-4"
|
||||
>
|
||||
{{ __('No slots available for this date.') }}
|
||||
<div v-else class="text-ink-red-3">
|
||||
{{ __('No slots available for the selected course.') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -67,14 +64,15 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import {
|
||||
call,
|
||||
createResource,
|
||||
dayjs,
|
||||
Dialog,
|
||||
createResource,
|
||||
Select,
|
||||
FormControl,
|
||||
toast,
|
||||
} from 'frappe-ui'
|
||||
import { reactive, watch, inject } from 'vue'
|
||||
import { ref, watch, inject } from 'vue'
|
||||
import { Calendar } from 'lucide-vue-next'
|
||||
import { formatTime } from '@/utils/'
|
||||
|
||||
const user = inject('$user')
|
||||
@@ -96,7 +94,7 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const evaluation = reactive({
|
||||
const evaluation = ref({
|
||||
course: '',
|
||||
date: '',
|
||||
start_time: '',
|
||||
@@ -120,21 +118,36 @@ const createEvaluation = createResource({
|
||||
})
|
||||
|
||||
function submitEvaluation(close) {
|
||||
createEvaluation.submit(evaluation, {
|
||||
call('frappe.client.insert', {
|
||||
doc: {
|
||||
doctype: 'LMS Certificate Request',
|
||||
batch_name: evaluation.value.batch,
|
||||
...evaluation.value,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
evaluations.value.reload()
|
||||
close()
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err.messages?.[0] || err)
|
||||
toast.warning(__(err.messages?.[0] || err))
|
||||
})
|
||||
/* createEvaluation.submit(evaluation.value, {
|
||||
validate() {
|
||||
if (!evaluation.course) {
|
||||
if (!evaluation.value.course) {
|
||||
return 'Please select a course.'
|
||||
}
|
||||
if (!evaluation.date) {
|
||||
if (!evaluation.value.date) {
|
||||
return 'Please select a date.'
|
||||
}
|
||||
if (!evaluation.start_time) {
|
||||
if (!evaluation.value.start_time) {
|
||||
return 'Please select a slot.'
|
||||
}
|
||||
if (dayjs(evaluation.date).isBefore(dayjs(), 'day')) {
|
||||
if (dayjs(evaluation.value.date).isBefore(dayjs(), 'day')) {
|
||||
return 'Please select a future date.'
|
||||
}
|
||||
if (dayjs(evaluation.date).isAfter(dayjs(props.endDate), 'day')) {
|
||||
if (dayjs(evaluation.value.date).isAfter(dayjs(props.endDate), 'day')) {
|
||||
return `Please select a date before the end date ${dayjs(
|
||||
props.endDate
|
||||
).format('DD MMMM YYYY')}.`
|
||||
@@ -148,7 +161,7 @@ function submitEvaluation(close) {
|
||||
console.log(err.messages?.[0] || err)
|
||||
toast.warning(__(err.messages?.[0] || err), { duration: 10000 })
|
||||
},
|
||||
})
|
||||
}) */
|
||||
}
|
||||
|
||||
const getCourses = () => {
|
||||
@@ -163,7 +176,7 @@ const getCourses = () => {
|
||||
}
|
||||
|
||||
if (courses.length === 1) {
|
||||
evaluation.course = courses[0].value
|
||||
evaluation.value.course = courses[0].value
|
||||
}
|
||||
|
||||
return courses
|
||||
@@ -174,13 +187,12 @@ const slots = createResource({
|
||||
makeParams(values) {
|
||||
return {
|
||||
course: values.course,
|
||||
date: values.date,
|
||||
batch: props.batch,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
watch(
|
||||
/* watch(
|
||||
() => evaluation.date,
|
||||
(date) => {
|
||||
evaluation.start_time = ''
|
||||
@@ -189,19 +201,18 @@ watch(
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
*/
|
||||
watch(
|
||||
() => evaluation.course,
|
||||
() => evaluation.value.course,
|
||||
(course) => {
|
||||
evaluation.date = ''
|
||||
evaluation.start_time = ''
|
||||
slots.reset()
|
||||
slots.reload(evaluation.value)
|
||||
}
|
||||
)
|
||||
|
||||
const saveSlot = (slot) => {
|
||||
evaluation.start_time = slot.start_time
|
||||
evaluation.end_time = slot.end_time
|
||||
evaluation.day = slot.day
|
||||
const saveSlot = (slot, row) => {
|
||||
evaluation.value.start_time = slot.start_time
|
||||
evaluation.value.end_time = slot.end_time
|
||||
evaluation.value.date = row.date
|
||||
evaluation.value.day = row.day
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -144,6 +144,20 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="batch.data.evaluation_end_date && isStudent"
|
||||
class="text-sm leading-5 bg-surface-amber-1 text-ink-amber-3 p-2 rounded-md mb-10"
|
||||
>
|
||||
{{ __('The last day to schedule your evaluations is ') }}
|
||||
<span class="font-medium">
|
||||
{{
|
||||
dayjs(batch.data.evaluation_end_date).format('DD MMMM YYYY')
|
||||
}} </span
|
||||
>.
|
||||
{{
|
||||
__('Please make sure to schedule your evaluation before this date.')
|
||||
}}
|
||||
</div>
|
||||
<div v-if="dayjs().isSameOrAfter(dayjs(batch.data.start_date))">
|
||||
<div class="text-ink-gray-7 font-semibold mb-2">
|
||||
{{ __('Feedback') }}
|
||||
|
||||
@@ -6,7 +6,7 @@ from datetime import datetime
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_time, getdate
|
||||
from frappe.utils import add_days, get_time, getdate, nowdate
|
||||
|
||||
from lms.lms.utils import get_evaluator
|
||||
|
||||
@@ -58,33 +58,126 @@ class CourseEvaluator(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_schedule(course, date, batch=None):
|
||||
def get_schedule(course, batch=None):
|
||||
evaluator = get_evaluator(course, batch)
|
||||
day = datetime.strptime(date, "%Y-%m-%d").strftime("%A")
|
||||
start_date = nowdate()
|
||||
end_date = get_end_date(start_date, batch)
|
||||
print(start_date, end_date)
|
||||
all_slots = get_all_slots(evaluator, start_date, end_date)
|
||||
booked_slots = get_booked_slots(evaluator, start_date, end_date)
|
||||
all_slots = remove_booked_slots(all_slots, booked_slots)
|
||||
return all_slots
|
||||
|
||||
all_slots = frappe.get_all(
|
||||
|
||||
def get_all_slots(evaluator, start_date, end_date):
|
||||
schedule = get_evaluator_schedule(evaluator)
|
||||
unavailable_dates = get_unavailable_dates(evaluator)
|
||||
all_slots = []
|
||||
current_date = getdate(start_date)
|
||||
end_date = getdate(end_date)
|
||||
|
||||
while current_date <= end_date:
|
||||
if current_date in unavailable_dates:
|
||||
current_date = add_days(current_date, 1)
|
||||
continue
|
||||
day_of_week = current_date.strftime("%A")
|
||||
slots_for_day = [x for x in schedule if x.day == day_of_week]
|
||||
for slot in slots_for_day:
|
||||
all_slots.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"day": day_of_week,
|
||||
"date": current_date,
|
||||
"start_time": slot.start_time,
|
||||
"end_time": slot.end_time,
|
||||
}
|
||||
)
|
||||
)
|
||||
current_date = add_days(current_date, 1)
|
||||
return all_slots
|
||||
|
||||
|
||||
def get_evaluator_schedule(evaluator):
|
||||
return frappe.get_all(
|
||||
"Evaluator Schedule",
|
||||
filters={
|
||||
"parent": evaluator,
|
||||
"day": day,
|
||||
},
|
||||
fields=["day", "start_time", "end_time"],
|
||||
order_by="start_time",
|
||||
)
|
||||
|
||||
booked_slots = frappe.get_all(
|
||||
|
||||
def get_booked_slots(evaluator, start_date, end_date):
|
||||
date = ["between", [start_date, end_date]]
|
||||
return frappe.get_all(
|
||||
"LMS Certificate Request",
|
||||
filters={
|
||||
"evaluator": evaluator,
|
||||
"date": date,
|
||||
"status": ["!=", "Cancelled"],
|
||||
},
|
||||
fields=["start_time", "day"],
|
||||
fields=["start_time", "day", "date"],
|
||||
)
|
||||
|
||||
for slot in booked_slots:
|
||||
same_slot = [x for x in all_slots if x.start_time == slot.start_time and x.day == slot.day]
|
||||
if len(same_slot):
|
||||
all_slots.remove(same_slot[0])
|
||||
|
||||
return all_slots
|
||||
def remove_booked_slots(all_slots, booked_slots):
|
||||
slots_to_remove = []
|
||||
for slot in all_slots:
|
||||
for booked in booked_slots:
|
||||
if slot.date == booked.date and slot.start_time == booked.start_time:
|
||||
slots_to_remove.append(slot)
|
||||
|
||||
for slot in slots_to_remove:
|
||||
all_slots.remove(slot)
|
||||
|
||||
return group_slots_by_date(all_slots)
|
||||
|
||||
|
||||
def group_slots_by_date(all_slots):
|
||||
slots_by_date = []
|
||||
dates_included = set()
|
||||
for slot in all_slots:
|
||||
date_str = slot.get("date").strftime("%Y-%m-%d")
|
||||
if date_str not in dates_included:
|
||||
slots_by_date.append({"date": date_str, "day": slot.day, "slots": []})
|
||||
dates_included.add(date_str)
|
||||
|
||||
for date_slot in slots_by_date:
|
||||
if date_slot.get("date") == date_str:
|
||||
date_slot.get("slots").append(
|
||||
{
|
||||
"start_time": slot.get("start_time"),
|
||||
"end_time": slot.get("end_time"),
|
||||
}
|
||||
)
|
||||
return slots_by_date
|
||||
|
||||
|
||||
def get_evaluator_availability(evaluator):
|
||||
return frappe.db.get_value(
|
||||
"Course Evaluator", evaluator, ["unavailable_from", "unavailable_to"], as_dict=1
|
||||
)
|
||||
|
||||
|
||||
def get_unavailable_dates(evaluator):
|
||||
availability = get_evaluator_availability(evaluator)
|
||||
unavailable_dates = []
|
||||
if availability.unavailable_from and availability.unavailable_to:
|
||||
current_date = getdate(availability.unavailable_from)
|
||||
end_date = getdate(availability.unavailable_to)
|
||||
|
||||
while current_date <= end_date:
|
||||
unavailable_dates.append(current_date)
|
||||
current_date = add_days(current_date, 1)
|
||||
return unavailable_dates
|
||||
|
||||
|
||||
def get_end_date(start_date, batch=None):
|
||||
end_date = add_days(start_date, 30)
|
||||
if batch:
|
||||
batch_end_date = frappe.db.get_value("LMS Batch", batch, "evaluation_end_date")
|
||||
if batch_end_date:
|
||||
end_date = getdate(batch_end_date)
|
||||
|
||||
return end_date
|
||||
|
||||
Reference in New Issue
Block a user