1.1.1
This commit is contained in:
parent
7073c73196
commit
53c533c3c6
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
*/node_modules/
|
*/node_modules/
|
||||||
*/build/
|
*/build/
|
||||||
|
|
||||||
|
**/node_modules/
|
||||||
|
**/build/
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
File diff suppressed because one or more lines are too long
28
backend/src/db/api/studentassessment.js
Normal file
28
backend/src/db/api/studentassessment.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const db = require('../models');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bulk create student assessment records for import.
|
||||||
|
* data: array of items with course, student, assessment_name, score, max_score, date
|
||||||
|
* options.currentUser.id is used as createdById
|
||||||
|
*/
|
||||||
|
async function bulkCreate(data, options) {
|
||||||
|
if (!data || !data.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const { currentUser } = options;
|
||||||
|
const records = data.map(item => ({
|
||||||
|
courseId: item.course,
|
||||||
|
studentId: item.student,
|
||||||
|
assessment_name: item.assessment_name,
|
||||||
|
score: item.score,
|
||||||
|
max_score: item.max_score,
|
||||||
|
date: item.date,
|
||||||
|
importHash: null,
|
||||||
|
createdById: currentUser.id,
|
||||||
|
}));
|
||||||
|
return await db.studentassessment.bulkCreate(records);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
bulkCreate,
|
||||||
|
};
|
||||||
1
frontend/json/runtimeError.json
Normal file
1
frontend/json/runtimeError.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
124
frontend/src/pages/course-files.tsx
Normal file
124
frontend/src/pages/course-files.tsx
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import axios from 'axios';
|
||||||
|
import {
|
||||||
|
SectionTitle,
|
||||||
|
FormFilePicker,
|
||||||
|
BaseButton,
|
||||||
|
LoadingSpinner,
|
||||||
|
NotificationBar,
|
||||||
|
} from '../components';
|
||||||
|
|
||||||
|
// CAA-mandated document sections
|
||||||
|
export const sections = [
|
||||||
|
'Course Specification',
|
||||||
|
'Teaching & Delivery Plan',
|
||||||
|
'Assessment Plan',
|
||||||
|
'Sample Assessments & Solutions',
|
||||||
|
'Grading Rubrics',
|
||||||
|
'Student Performance Report',
|
||||||
|
'Moderation Report',
|
||||||
|
'Verification Report',
|
||||||
|
'Course Report & Recommendations',
|
||||||
|
'Evidence of Continuous Improvement',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type SectionKey = typeof sections[number];
|
||||||
|
|
||||||
|
interface FileRecord {
|
||||||
|
id: string;
|
||||||
|
sectionType: SectionKey;
|
||||||
|
filename: string;
|
||||||
|
status: string;
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CourseFilesPage: React.FC = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { courseId } = router.query;
|
||||||
|
const [files, setFiles] = useState<FileRecord[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!courseId) return;
|
||||||
|
fetchFiles();
|
||||||
|
}, [courseId]);
|
||||||
|
|
||||||
|
const fetchFiles = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`/api/course_files?courseId=${courseId}`);
|
||||||
|
setFiles(res.data);
|
||||||
|
setError(null);
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err.message || 'Could not load files');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpload = async (section: SectionKey, file: File) => {
|
||||||
|
setLoading(true);
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('file', file);
|
||||||
|
form.append('courseId', String(courseId));
|
||||||
|
form.append('sectionType', section);
|
||||||
|
try {
|
||||||
|
await axios.post('/api/course_files', form, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
});
|
||||||
|
await fetchFiles();
|
||||||
|
setError(null);
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err.message || 'Upload failed');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) return <LoadingSpinner />;
|
||||||
|
if (error) return <NotificationBar message={error} type="error" />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4">
|
||||||
|
<SectionTitle title="Manage Course Files" />
|
||||||
|
<div className="mb-2 text-sm text-gray-600">Course ID: {courseId}</div>
|
||||||
|
<table className="w-full table-auto border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-gray-100">
|
||||||
|
<th className="text-left px-2 py-1">Section</th>
|
||||||
|
<th className="text-left px-2 py-1">Status</th>
|
||||||
|
<th className="text-left px-2 py-1">File</th>
|
||||||
|
<th className="text-left px-2 py-1">Comments</th>
|
||||||
|
<th className="px-2 py-1">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{sections.map((section) => {
|
||||||
|
const record = files.find((f) => f.sectionType === section);
|
||||||
|
return (
|
||||||
|
<tr key={section} className="border-t">
|
||||||
|
<td className="px-2 py-1">{section}</td>
|
||||||
|
<td className="px-2 py-1">{record?.status || 'Not submitted'}</td>
|
||||||
|
<td className="px-2 py-1">{record?.filename || '-'}</td>
|
||||||
|
<td className="px-2 py-1">{record?.notes || '-'}</td>
|
||||||
|
<td className="px-2 py-1">
|
||||||
|
<FormFilePicker
|
||||||
|
onFileSelected={(file) => handleUpload(section, file)}
|
||||||
|
buttonText={record ? 'Replace' : 'Upload'}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className="mt-4">
|
||||||
|
<BaseButton onClick={fetchFiles}>Refresh</BaseButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CourseFilesPage;
|
||||||
Loading…
x
Reference in New Issue
Block a user