Skip to content

Commit 9634de8

Browse files
committed
feat: enhance update_archive_course functionality to support course transfer by name and category, and update related frontend services and components
1 parent deea8b5 commit 9634de8

5 files changed

Lines changed: 128 additions & 22 deletions

File tree

backend/app/api/services/courses.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ async def update_archive_course(
160160
):
161161
"""
162162
Update archive's course. Only admins can change archive's course.
163+
Supports both transferring to existing course by ID or creating new course by name and category.
163164
"""
164165
if not current_user.is_admin:
165166
raise HTTPException(
@@ -181,16 +182,59 @@ async def update_archive_course(
181182
detail="Archive not found"
182183
)
183184

184-
new_course_query = select(Course).where(Course.id == course_update.course_id)
185-
new_course_result = await db.execute(new_course_query)
186-
new_course = new_course_result.scalar_one_or_none()
185+
# Determine target course
186+
new_course = None
187187

188-
if not new_course:
188+
if course_update.course_id:
189+
# Check if trying to transfer to the same course
190+
if course_update.course_id == course_id:
191+
raise HTTPException(
192+
status_code=status.HTTP_400_BAD_REQUEST,
193+
detail="Cannot transfer archive to the same course"
194+
)
195+
196+
# Transfer to existing course by ID
197+
new_course_query = select(Course).where(Course.id == course_update.course_id)
198+
new_course_result = await db.execute(new_course_query)
199+
new_course = new_course_result.scalar_one_or_none()
200+
201+
if not new_course:
202+
raise HTTPException(
203+
status_code=status.HTTP_404_NOT_FOUND,
204+
detail="Target course not found"
205+
)
206+
elif course_update.course_name and course_update.course_category:
207+
# Transfer to course by name and category, create if not exists
208+
new_course_query = select(Course).where(
209+
Course.name == course_update.course_name,
210+
Course.category == course_update.course_category
211+
)
212+
new_course_result = await db.execute(new_course_query)
213+
new_course = new_course_result.scalar_one_or_none()
214+
215+
if new_course:
216+
# Check if trying to transfer to the same course
217+
if new_course.id == course_id:
218+
raise HTTPException(
219+
status_code=status.HTTP_400_BAD_REQUEST,
220+
detail="Cannot transfer archive to the same course"
221+
)
222+
else:
223+
# Create new course if it doesn't exist
224+
new_course = Course(
225+
name=course_update.course_name,
226+
category=course_update.course_category
227+
)
228+
db.add(new_course)
229+
await db.commit()
230+
await db.refresh(new_course)
231+
else:
189232
raise HTTPException(
190-
status_code=status.HTTP_404_NOT_FOUND,
191-
detail="Target course not found"
233+
status_code=status.HTTP_400_BAD_REQUEST,
234+
detail="Either course_id or both course_name and course_category must be provided"
192235
)
193-
archive.course_id = course_update.course_id
236+
237+
archive.course_id = new_course.id
194238
archive.updated_at = datetime.now(timezone.utc)
195239

196240
await db.commit()
@@ -200,7 +244,7 @@ async def update_archive_course(
200244
"message": f"Archive moved to course '{new_course.name}'",
201245
"archive_id": archive.id,
202246
"old_course_id": course_id,
203-
"new_course_id": course_update.course_id
247+
"new_course_id": new_course.id
204248
}
205249

206250
@router.delete("/{course_id}/archives/{archive_id}")

backend/app/models/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ class Config:
191191
from_attributes = True
192192

193193
class ArchiveUpdateCourse(BaseModel):
194-
course_id: int
194+
course_id: Optional[int] = None
195+
course_name: Optional[str] = None
196+
course_category: Optional[CourseCategory] = None
195197

196198

frontend/src/api/services/archives.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,16 @@ export const archiveService = {
3232
course_id: newCourseId,
3333
});
3434
},
35+
36+
updateArchiveCourseByCategoryAndName(
37+
courseId,
38+
archiveId,
39+
courseName,
40+
courseCategory
41+
) {
42+
return api.patch(`/courses/${courseId}/archives/${archiveId}/course`, {
43+
course_name: courseName,
44+
course_category: courseCategory,
45+
});
46+
},
3547
};

frontend/src/components/UploadArchiveDialog.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
</div>
4242

4343
<div class="flex flex-column gap-2">
44-
<label>科目名稱</label>
44+
<label>科目</label>
4545
<AutoComplete
4646
v-model="form.subject"
4747
:suggestions="availableSubjects"
@@ -50,7 +50,7 @@
5050
@focus="() => searchSubject({ query: '' })"
5151
@click="() => searchSubject({ query: '' })"
5252
optionLabel="name"
53-
placeholder="選擇科目"
53+
placeholder="搜尋或輸入科目名稱"
5454
class="w-full"
5555
:disabled="!form.category"
5656
dropdown
@@ -75,7 +75,7 @@
7575
@focus="() => searchProfessor({ query: '' })"
7676
@click="() => searchProfessor({ query: '' })"
7777
optionLabel="name"
78-
placeholder="選擇教授"
78+
placeholder="搜尋或輸入教授名稱"
7979
class="w-full"
8080
:disabled="!form.subject"
8181
dropdown

frontend/src/views/Archive.vue

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@
479479
@focus="() => searchTargetCourse({ query: '' })"
480480
@click="() => searchTargetCourse({ query: '' })"
481481
optionLabel="label"
482-
placeholder="搜尋目標課程"
482+
placeholder="搜尋或輸入目標課程名稱"
483483
class="w-full"
484484
:disabled="!editForm.targetCategory"
485485
dropdown
@@ -1060,9 +1060,18 @@ watch(
10601060
() => groupedArchives.value,
10611061
(newGroups) => {
10621062
if (newGroups.length) {
1063-
expandedPanels.value = newGroups
1064-
.slice(0, 3)
1065-
.map((group) => group.year.toString());
1063+
// Only set default expanded panels if no panels are currently expanded
1064+
if (expandedPanels.value.length === 0) {
1065+
expandedPanels.value = newGroups
1066+
.slice(0, 3)
1067+
.map((group) => group.year.toString());
1068+
} else {
1069+
// Keep existing expanded panels that are still valid
1070+
const validYears = newGroups.map((group) => group.year.toString());
1071+
expandedPanels.value = expandedPanels.value.filter((panel) =>
1072+
validYears.includes(panel)
1073+
);
1074+
}
10661075
}
10671076
},
10681077
{ immediate: true }
@@ -1218,15 +1227,32 @@ const handleEdit = async () => {
12181227
}
12191228
);
12201229
1221-
if (editForm.value.shouldTransfer && editForm.value.targetCourseId) {
1222-
await archiveService.updateArchiveCourse(
1223-
selectedCourse.value,
1224-
editForm.value.id,
1225-
editForm.value.targetCourseId
1226-
);
1230+
if (editForm.value.shouldTransfer && editForm.value.targetCategory) {
1231+
if (editForm.value.targetCourseId) {
1232+
// Transfer to existing course
1233+
await archiveService.updateArchiveCourse(
1234+
selectedCourse.value,
1235+
editForm.value.id,
1236+
editForm.value.targetCourseId
1237+
);
1238+
} else if (editForm.value.targetCourse) {
1239+
// Transfer to new course (create if not exists)
1240+
await archiveService.updateArchiveCourseByCategoryAndName(
1241+
selectedCourse.value,
1242+
editForm.value.id,
1243+
editForm.value.targetCourse,
1244+
editForm.value.targetCategory
1245+
);
1246+
}
12271247
}
12281248
12291249
await fetchArchives();
1250+
1251+
// If transfer was performed, refresh the course list to show the new course
1252+
if (editForm.value.shouldTransfer) {
1253+
await fetchCourses();
1254+
}
1255+
12301256
closeEditDialog();
12311257
12321258
const successMessage = editForm.value.shouldTransfer
@@ -1396,9 +1422,31 @@ const onTargetCourseSelect = (event) => {
13961422
if (event.value && typeof event.value === "object") {
13971423
editForm.value.targetCourse = event.value.label;
13981424
editForm.value.targetCourseId = event.value.id;
1425+
} else if (typeof event.value === "string") {
1426+
// User typed a new course name
1427+
editForm.value.targetCourse = event.value;
1428+
editForm.value.targetCourseId = null;
13991429
}
14001430
};
14011431
1432+
// Handle direct input of course name
1433+
watch(
1434+
() => editForm.value.targetCourse,
1435+
(newValue) => {
1436+
if (typeof newValue === "string" && newValue) {
1437+
// Check if it's an existing course
1438+
const existingCourse = allAvailableCoursesForTransfer.value.find(
1439+
(course) => course.label === newValue
1440+
);
1441+
if (existingCourse) {
1442+
editForm.value.targetCourseId = existingCourse.id;
1443+
} else {
1444+
editForm.value.targetCourseId = null;
1445+
}
1446+
}
1447+
}
1448+
);
1449+
14021450
watch(
14031451
() => editForm.value.targetCategory,
14041452
() => {

0 commit comments

Comments
 (0)