Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,8 +1,3 @@
|
|||||||
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
@ -1,28 +0,0 @@
|
|||||||
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 +0,0 @@
|
|||||||
{}
|
|
||||||
@ -1,124 +0,0 @@
|
|||||||
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