This commit is contained in:
Flatlogic Bot 2025-05-05 10:49:43 +00:00
parent 7073c73196
commit 53c533c3c6
5 changed files with 161 additions and 4 deletions

5
.gitignore vendored
View File

@ -1,3 +1,8 @@
node_modules/
*/node_modules/
*/build/
**/node_modules/
**/build/
.DS_Store
.env

File diff suppressed because one or more lines are too long

View 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,
};

View File

@ -0,0 +1 @@
{}

View 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;