Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 33 additions & 22 deletions app/components/AppCalendar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,7 @@

const { data: terms } = await useFetch('/api/terms')
const { data: rooms } = await useFetch('/api/rooms')
const { data: allSubjects } = await useFetch('/api/Subjects')

Check failure on line 1002 in app/components/AppCalendar.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 22)

'allSubjects' is assigned a value but never used. Allowed unused vars must match /^_/u

const availableSlots = ref([])
const selectedSlot = ref(null)
Expand Down Expand Up @@ -1084,7 +1084,7 @@
}
})

const roomOptions = computed(() => {

Check failure on line 1087 in app/components/AppCalendar.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 22)

'roomOptions' is assigned a value but never used. Allowed unused vars must match /^_/u
if (!rooms.value) return [{ label: 'ไม่ระบุห้องเรียน', value: null }]
const opts = rooms.value.map(r => ({
label: r.room_name,
Expand Down Expand Up @@ -1148,7 +1148,7 @@
end: info.endStr.split('T')[0]
}
},
viewDidMount: (info) => {

Check failure on line 1151 in app/components/AppCalendar.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 22)

'info' is defined but never used. Allowed unused args must match /^_/u
// แก้ไขสีพื้นหลังของ header หลังจาก calendar render เสร็จ
setTimeout(() => {
const headerCells = document.querySelectorAll('.fc-col-header-cell')
Expand Down Expand Up @@ -1454,6 +1454,7 @@
classes: selectedClasses.map(c => ({
subjectId: c.subjectId,
sectionId: c.sectionId,
sectionIds: c.sectionIds,
roomId: c.roomId,
duration: c.duration,
subjectName: c.subjectName
Expand Down Expand Up @@ -1498,6 +1499,7 @@
classes: JSON.stringify(rescheduledClasses.map(c => ({
subjectId: c.subjectId,
sectionId: c.sectionId,
sectionIds: c.sectionIds,
roomId: c.roomId,
duration: c.duration,
subjectName: c.subjectName
Expand Down Expand Up @@ -1657,32 +1659,41 @@
let endTime = addHours(currentTime, cls.duration)

// ถ้าเวลาจบคร่อม 12:00 (หมายถึงช่วง 12:00-13:00) ให้บวกเพิ่มอีก 1 ชม. เพื่อข้ามพักเที่ยง
// เช่น เริ่ม 11:00 เรียน 2 ชม. ปกติจบ 13:00 แต่ต้องจบ 14:00 เพราะพัก 12:00-13:00
const startHour = parseInt(currentTime.split(':')[0])
const endHour = parseInt(endTime.split(':')[0])
if (startHour < 12 && endHour > 12) {
endTime = addHours(endTime, 1)
}

const res = await $fetch('/api/makeup-classes', {
method: 'POST',
body: {
original_date: selectedSlot.value.missedDate || absenceForm.value.date,
original_time_slot: '',
makeup_date: selectedSlot.value.date,
makeup_time_start: currentTime,
makeup_time_end: endTime,
teacher_id: absenceForm.value.teacherId,
section_id: cls.sectionId,
subject_id: cls.subjectId,
room_id: cls.roomId || null,
term: absenceForm.value.term,
status: 'confirmed',
notes: makeupForm.value.notes,
exclude_makeup_ids: createdIds // ป้องกัน false-positive จากวิชาก่อนหน้าในกลุ่มเดียวกัน
}
})
createdIds.push(res.id_makeup)
const sectionIds = cls.sectionIds && cls.sectionIds.length > 0 ? cls.sectionIds : [cls.sectionId]
const makeupIdsForThisClass = []

for (const secId of sectionIds) {
if (!secId) continue
const res = await $fetch('/api/makeup-classes', {
method: 'POST',
body: {
original_date: selectedSlot.value.missedDate || absenceForm.value.date,
original_time_slot: '',
makeup_date: selectedSlot.value.date,
makeup_time_start: currentTime,
makeup_time_end: endTime,
teacher_id: absenceForm.value.teacherId,
section_id: secId,
subject_id: cls.subjectId,
room_id: cls.roomId || null,
term: absenceForm.value.term,
status: 'confirmed',
notes: makeupForm.value.notes,
exclude_makeup_ids: createdIds // ป้องกัน false-positive จากวิชาก่อนหน้าในกลุ่มเดียวกัน
}
})
createdIds.push(res.id_makeup)
makeupIdsForThisClass.push(res.id_makeup)
}

Check failure on line 1694 in app/components/AppCalendar.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 22)

Trailing spaces not allowed
// Save the ids for the calendar event creation
cls._makeupIds = makeupIdsForThisClass
currentTime = endTime
}
} else {
Expand All @@ -1709,7 +1720,7 @@
let currentTime = selectedSlot.value.timeStart
for (let i = 0; i < classes.length; i++) {
const cls = classes[i]
const idMakeup = createdIds[i]
const idsMakeup = cls._makeupIds || []

if (currentTime === '12:00') currentTime = '13:00'

Expand All @@ -1735,7 +1746,7 @@
originalDate: selectedSlot.value.missedDate || absenceForm.value.date,
term: absenceForm.value.term,
classes: JSON.stringify([cls]),
makeupClassIds: JSON.stringify([idMakeup]),
makeupClassIds: JSON.stringify(idsMakeup),
roomId: cls.roomId,
roomName: props.rooms?.find(r => Number(r.id_room) === Number(cls.roomId))?.room_name || '',
description: `วิชา: ${cls.subjectName}${cls.roomId ? '\nห้อง: ' + (props.rooms?.find(r => Number(r.id_room) === Number(cls.roomId))?.room_name || cls.roomId) : ''}${makeupForm.value.notes ? '\n' + makeupForm.value.notes : ''}`
Expand Down Expand Up @@ -1882,7 +1893,7 @@
if (props.classes) {
try {
selectedClassesList = typeof props.classes === 'string' ? JSON.parse(props.classes) : props.classes
} catch (e) {

Check failure on line 1896 in app/components/AppCalendar.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 22)

'e' is defined but never used
selectedClassesList = []
}
}
Expand Down Expand Up @@ -1980,7 +1991,7 @@
}

// Helper functions
const formatDate = (date) => {

Check failure on line 1994 in app/components/AppCalendar.vue

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 22)

'formatDate' is assigned a value but never used. Allowed unused vars must match /^_/u
if (!date) return ''
return new Date(date).toLocaleString('th-TH', {
year: 'numeric',
Expand Down
52 changes: 0 additions & 52 deletions create_test_data.js

This file was deleted.

2 changes: 0 additions & 2 deletions debug_output.txt

This file was deleted.

3 changes: 0 additions & 3 deletions diagnostic_output.txt

This file was deleted.

Empty file removed makeup.db
Empty file.
46 changes: 31 additions & 15 deletions server/api/teacher-classes-on-date.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,54 +55,66 @@ export default defineEventHandler(async (event) => {
// นับจำนวนชั่วโมงติดต่อกันของแต่ละวิชา
let currentSubject = null
let currentRoom = null
let currentSectionIdsStr = null
let startSlot = 0
let duration = 0

for (let i = 0; i < daySchedule.length; i++) {
const slot = daySchedule[i]
const slotValue = slot?.value
const slotRoom = slot?.room_id || null
const slotSectionIds = slot?.section_ids || null

// แปลงเป็น String เพื่อความชัวร์ในการเปรียบเทียบ (null/undefined จะกลายเป็น "null"/"undefined")
const valStr = slotValue ? String(slotValue) : null
const secStr = slotSectionIds ? JSON.stringify(slotSectionIds) : null

if (valStr && valStr !== 'null') {
// ถ้าเป็นวิชาเดิมและห้องเดิม
if (valStr === currentSubject && slotRoom === currentRoom) {
// ถ้าเป็นวิชาเดิมและห้องเดิมและกลุ่มเดิม
if (valStr === currentSubject && slotRoom === currentRoom && secStr === currentSectionIdsStr) {
duration++
} else {
// จบวิชาก่อนหน้า
if (currentSubject) {
classes.push(createClassObj(currentSubject, startSlot, duration, teacher_id, date, currentRoom))
classes.push(createClassObj(currentSubject, startSlot, duration, teacher_id, date, currentRoom, currentSectionIdsStr ? JSON.parse(currentSectionIdsStr) : null))
}

// เริ่มวิชาใหม่
currentSubject = valStr
currentRoom = slotRoom
currentSectionIdsStr = secStr
startSlot = i
duration = 1
}
} else {
// เจอช่องว่าง
if (currentSubject) {
classes.push(createClassObj(currentSubject, startSlot, duration, teacher_id, date, currentRoom))
classes.push(createClassObj(currentSubject, startSlot, duration, teacher_id, date, currentRoom, currentSectionIdsStr ? JSON.parse(currentSectionIdsStr) : null))
currentSubject = null
currentRoom = null
currentSectionIdsStr = null
}
}
}

// เก็บตกตัวสุดท้าย
if (currentSubject) {
classes.push(createClassObj(currentSubject, startSlot, duration, teacher_id, date, currentRoom))
classes.push(createClassObj(currentSubject, startSlot, duration, teacher_id, date, currentRoom, currentSectionIdsStr ? JSON.parse(currentSectionIdsStr) : null))
}

console.log('[teacher-classes-on-date] Merged classes:', classes)
return classes
})

function createClassObj(subjectId, startSlot, duration, teacherId, date, roomId) {
const sectionId = getSectionForSubject(subjectId)
function createClassObj(subjectId, startSlot, duration, teacherId, date, roomId, slotSectionIds) {
let sectionIds = []
if (Array.isArray(slotSectionIds) && slotSectionIds.length > 0) {
sectionIds = slotSectionIds
} else {
sectionIds = getSectionsForSubjectArray(subjectId)
}

const sectionId = sectionIds[0] || null

let hasMakeup = false
if (teacherId && date) {
Expand All @@ -121,7 +133,8 @@ function createClassObj(subjectId, startSlot, duration, teacherId, date, roomId)
subjectId,
subjectName: getSubjectName(subjectId),
sectionId,
sectionName: getSectionName(sectionId, subjectId),
sectionIds,
sectionName: sectionIds.map(id => getSectionNameByKey(id)).join(', ') || getSectionName(sectionId, subjectId),
roomId: finalRoomId,
startSlot,
duration,
Expand All @@ -137,14 +150,17 @@ function getSubjectName(subjectId) {
return result?.name_subject || 'ไม่ทราบชื่อวิชา'
}

function getSectionForSubject(subjectId) {
const stmt = db.prepare('SELECT id_section FROM SubjectSections WHERE id_subject = ? LIMIT 1')
const result = stmt.get(subjectId)
if (result) return result.id_section
function getSectionsForSubjectArray(subjectId) {
const stmt = db.prepare('SELECT id_section FROM SubjectSections WHERE id_subject = ?')
const results = stmt.all(subjectId)
return results.map(r => r.id_section)
}

const fallbackStmt = db.prepare('SELECT id_section FROM Subjects WHERE id_subject = ?')
const fallbackResult = fallbackStmt.get(subjectId)
return fallbackResult?.id_section || null
function getSectionNameByKey(sectionId) {
if (!sectionId) return ''
const stmt = db.prepare('SELECT section_name FROM sections WHERE id_section = ?')
const result = stmt.get(sectionId)
return result?.section_name || ''
}

function getSectionName(sectionId, subjectId) {
Expand Down
Binary file modified server/data/data.db
Binary file not shown.
27 changes: 19 additions & 8 deletions server/utils/autoSchedule.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ function getClassesOnDate(schedule, dateStr, subjectNameMap) {

// นับจำนวนชั่วโมงติดต่อกันของแต่ละวิชา
let currentSubject = null
let currentSectionIdsStr = null
let startSlot = 0
let duration = 0

Expand All @@ -197,22 +198,25 @@ function getClassesOnDate(schedule, dateStr, subjectNameMap) {
for (let i = 0; i < daySchedule.length; i++) {
const slot = daySchedule[i]
const slotValue = slot?.value
const slotSectionIds = slot?.section_ids || null

// แปลงเป็น String เพื่อความชัวร์ในการเปรียบเทียบ
const valStr = slotValue ? String(slotValue) : null
const secStr = slotSectionIds ? JSON.stringify(slotSectionIds) : null

if (valStr) {
// ถ้าเป็นวิชาเดิม
if (valStr === currentSubject) {
if (valStr === currentSubject && secStr === currentSectionIdsStr) {
duration++
} else {
// จบวิชาก่อนหน้า
if (currentSubject) {
classes.push(createClassObj(currentSubject, startSlot, duration, subjectNameMap))
classes.push(createClassObj(currentSubject, currentSectionIdsStr ? JSON.parse(currentSectionIdsStr) : null, startSlot, duration, subjectNameMap))
}

// เริ่มวิชาใหม่
currentSubject = valStr
currentSectionIdsStr = secStr
startSlot = i
duration = 1
}
Expand All @@ -222,20 +226,22 @@ function getClassesOnDate(schedule, dateStr, subjectNameMap) {
if (i === LUNCH_SLOT) {
const nextSlot = daySchedule[i + 1]
const nextVal = nextSlot?.value ? String(nextSlot.value) : null
if (nextVal === currentSubject) {
const nextSecStr = nextSlot?.section_ids ? JSON.stringify(nextSlot.section_ids) : null
if (nextVal === currentSubject && nextSecStr === currentSectionIdsStr) {
continue // ข้าม lunch slot แล้ววิชายังต่อเนื่อง
}
}
classes.push(createClassObj(currentSubject, startSlot, duration, subjectNameMap))
classes.push(createClassObj(currentSubject, currentSectionIdsStr ? JSON.parse(currentSectionIdsStr) : null, startSlot, duration, subjectNameMap))
currentSubject = null
currentSectionIdsStr = null
duration = 0
}
}
}

// เก็บตกตัวสุดท้าย
if (currentSubject) {
classes.push(createClassObj(currentSubject, startSlot, duration, subjectNameMap))
classes.push(createClassObj(currentSubject, currentSectionIdsStr ? JSON.parse(currentSectionIdsStr) : null, startSlot, duration, subjectNameMap))
}

// รวมวิชาเดิมที่แยกเป็นหลาย block (กรณีสอนกระจาย)
Expand All @@ -253,8 +259,13 @@ function getClassesOnDate(schedule, dateStr, subjectNameMap) {
return merged
}

function createClassObj(subjectId, startSlot, duration, subjectNameMap) {
const sectionIds = getSectionsForSubject(subjectId)
function createClassObj(subjectId, slotSectionIds, startSlot, duration, subjectNameMap) {
let sectionIds = []
if (Array.isArray(slotSectionIds) && slotSectionIds.length > 0) {
sectionIds = slotSectionIds
} else {
sectionIds = getSectionsForSubject(subjectId)
}
return {
subjectId,
subjectName: subjectNameMap[subjectId] || getSubjectName(subjectId),
Expand Down Expand Up @@ -474,7 +485,7 @@ export async function findAvailableSlotsForMultipleClasses(teacherId, missedDate
}

const totalDuration = classes.reduce((sum, c) => sum + c.duration, 0)
const allSectionIds = [...new Set(classes.map(c => c.sectionId).filter(Boolean))]
const allSectionIds = [...new Set(classes.flatMap(c => c.sectionIds || [c.sectionId]).filter(Boolean))]

const teacherSchedulesRaw = db.prepare('SELECT id_teacher, scheduleData FROM schedules WHERE term = ?').all(term)
const allTeacherSchedules = teacherSchedulesRaw.map((ts) => {
Expand Down
Empty file removed tmp.txt
Empty file.
Loading