Cleaned up the chromebooks view; Reorganized the admin view; Added functionality to import students and staff (still need to add/test functionality to edit them and delete them).
This commit is contained in:
124
imports/ui/Admin/AssetTypes.svelte
Normal file
124
imports/ui/Admin/AssetTypes.svelte
Normal file
@@ -0,0 +1,124 @@
|
||||
<script>
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import {onMount} from "svelte";
|
||||
import GridTable from "./../GridTable.svelte";
|
||||
import {writable} from "svelte/store";
|
||||
import TextField from '@smui/textfield';
|
||||
import HelperText from '@smui/textfield/helper-text';
|
||||
import {AssetTypes} from "../../api/asset-types";
|
||||
|
||||
onMount(async () => {
|
||||
Meteor.subscribe('assetTypes');
|
||||
});
|
||||
|
||||
const assetTypesColumns = [
|
||||
{
|
||||
key: "_id",
|
||||
title: "ID",
|
||||
value: v => v._id,
|
||||
minWidth: 20,
|
||||
weight: 1,
|
||||
cls: "id",
|
||||
}, {
|
||||
key: "name",
|
||||
title: "Name",
|
||||
value: v => v.name,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "name",
|
||||
}, {
|
||||
key: "description",
|
||||
title: "Description",
|
||||
value: v => v.description,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "description",
|
||||
},
|
||||
];
|
||||
const assetTypesActions = {
|
||||
title: "Actions",
|
||||
headerWidgets: [
|
||||
{icon: "add_box", action: () => {editedAssetType.set({});}, tooltip: "Add a new asset type."}
|
||||
],
|
||||
rowWidgets: [
|
||||
{icon: "add_circle", action: (v) => {editedAssetType.set(v)}},
|
||||
{icon: "delete", action: (v) => {deleteAssetType(v)}}
|
||||
],
|
||||
};
|
||||
let editedAssetType = writable(null);
|
||||
const onAssetTypeSelection = (e) => {
|
||||
}
|
||||
let dirtyAssetType;
|
||||
// Copy the edited value when ever it changes, set some defaults for a new value object (to make the view happy).
|
||||
editedAssetType.subscribe(v => {dirtyAssetType = Object.assign({name: "", description: ""}, v)});
|
||||
// Load the sites (reactive).
|
||||
let assetTypes = AssetTypes.find({});
|
||||
const deleteAssetType = assetType => {
|
||||
//TODO:
|
||||
};
|
||||
const applyAssetTypeChanges = () => {
|
||||
if(dirtyAssetType._id)
|
||||
Meteor.call("assetTypes.update", dirtyAssetType._id, dirtyAssetType.name, dirtyAssetType.description);
|
||||
else
|
||||
Meteor.call("assetTypes.add", dirtyAssetType.name, dirtyAssetType.description);
|
||||
editedAssetType.set(null);
|
||||
dirtyAssetType = null;
|
||||
}
|
||||
const rejectAssetTypeChanges = () => {
|
||||
editedAssetType.set(null);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<h2>Asset Types</h2>
|
||||
<GridTable bind:rows={assetTypes} columns="{assetTypesColumns}" actions="{assetTypesActions}" rowKey="{(v) => {return v._id}}" bind:edited="{editedAssetType}" on:selection={onAssetTypeSelection}>
|
||||
{#if dirtyAssetType}
|
||||
<div class="editorContainer">
|
||||
<div style="grid-column: 1/span 1">
|
||||
<TextField type="text" style="width: 100%" bind:value={dirtyAssetType.name} label="Name">
|
||||
<HelperText slot="helper">Provide a unique name for the asset type.</HelperText>
|
||||
</TextField>
|
||||
<TextField type="text" style="width: 100%" bind:value={dirtyAssetType.description} label="Description">
|
||||
<HelperText slot="helper">A detailed description.</HelperText>
|
||||
</TextField>
|
||||
</div>
|
||||
|
||||
<button type="button" style="grid-column: 2/span 1;" class="button accept-button material-icons material-symbols-outlined" on:click={applyAssetTypeChanges}>check</button>
|
||||
<button type="button" style="grid-column: 3/span 1;" class="button reject-button material-icons material-symbols-outlined" on:click={rejectAssetTypeChanges}>close</button>
|
||||
</div>
|
||||
{/if}
|
||||
</GridTable>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
.editorContainer {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(10px, 1fr) minmax(3rem, 3rem) minmax(3rem, 3rem);
|
||||
}
|
||||
.accept-button, .reject-button {
|
||||
font-size: .8rem;
|
||||
padding: .6rem;
|
||||
margin: auto;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
border: 0;
|
||||
font-weight: 800;
|
||||
alignment: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.accept-button {
|
||||
background-color: rgba(61, 148, 61, 0.91);
|
||||
}
|
||||
.reject-button {
|
||||
background-color: rgba(176, 64, 64, 0.61);
|
||||
}
|
||||
.accept-button:hover {
|
||||
background-color: rgb(61, 148, 61);
|
||||
}
|
||||
.reject-button:hover {
|
||||
background-color: rgb(176, 64, 64);
|
||||
}
|
||||
</style>
|
||||
117
imports/ui/Admin/Sites.svelte
Normal file
117
imports/ui/Admin/Sites.svelte
Normal file
@@ -0,0 +1,117 @@
|
||||
<script>
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import {onMount} from "svelte";
|
||||
import {Sites} from "../../api/sites";
|
||||
import GridTable from "./../GridTable.svelte";
|
||||
import {writable} from "svelte/store";
|
||||
import TextField from '@smui/textfield';
|
||||
import HelperText from '@smui/textfield/helper-text';
|
||||
|
||||
onMount(async () => {
|
||||
Meteor.subscribe('sites');
|
||||
});
|
||||
|
||||
// Should not be needed now. Did not work well - had a bug somewhere.
|
||||
// const fixRecords = () => {Meteor.call("admin.fixRecords");}
|
||||
|
||||
const siteColumns = [
|
||||
{
|
||||
key: "name",
|
||||
title: "Name",
|
||||
value: v => v.name,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "name",
|
||||
},
|
||||
];
|
||||
const siteActions = {
|
||||
title: "Actions",
|
||||
headerWidgets: [
|
||||
{icon: "add_box", action: () => {editedSite.set({name: ""});}, tooltip: "Add a new Site."}
|
||||
],
|
||||
rowWidgets: [
|
||||
{icon: "add_circle", action: (v) => {editedSite.set(v)}},
|
||||
{icon: "delete", action: (v) => {deleteSite(v)}}
|
||||
],
|
||||
};
|
||||
const deleteSite = site => {
|
||||
//TODO:
|
||||
};
|
||||
// Create a holder for the site being edited. This allows us to clear the editor when the user finishes, and allows the table or parent view to setup the editor.
|
||||
let editedSite = writable(null);
|
||||
let dirtySite;
|
||||
// Copy the edited site when ever it changes, set some defaults for a new site object (to make the view happy).
|
||||
editedSite.subscribe(site => {dirtySite = Object.assign({name:""}, site)});
|
||||
// Load the sites (reactive).
|
||||
let sites = Sites.find({});
|
||||
const applySiteChanges = () => {
|
||||
if(dirtySite._id)
|
||||
Meteor.call("sites.update", dirtySite._id, dirtySite.name);
|
||||
else
|
||||
Meteor.call("sites.add", dirtySite.name);
|
||||
editedSite.set(null);
|
||||
dirtySite = null;
|
||||
}
|
||||
const rejectSiteChanges = () => {
|
||||
editedSite.set(null);
|
||||
}
|
||||
let selectedSite = null;
|
||||
const onSiteSelection = (e) => {
|
||||
selectedSite = Sites.findOne({_id: e.detail});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<h2>Sites</h2>
|
||||
<GridTable bind:rows={sites} columns="{siteColumns}" actions="{siteActions}" rowKey="{(v) => {return v._id}}" bind:edited="{editedSite}" on:selection={onSiteSelection}>
|
||||
{#if dirtySite}
|
||||
<div class="editorContainer">
|
||||
<div style="grid-column: 1/span 1">
|
||||
<TextField type="text" style="width: 100%" bind:value={dirtySite.name} label="Name">
|
||||
<HelperText slot="helper">Provide a unique name for the site.</HelperText>
|
||||
</TextField>
|
||||
</div>
|
||||
|
||||
<button type="button" style="grid-column: 2/span 1;" class="button accept-button material-icons material-symbols-outlined" on:click={applySiteChanges}>
|
||||
check
|
||||
</button>
|
||||
<button type="button" style="grid-column: 3/span 1;" class="button reject-button material-icons material-symbols-outlined" on:click={rejectSiteChanges}>
|
||||
close
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</GridTable>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
.editorContainer {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(10px, 1fr) minmax(3rem, 3rem) minmax(3rem, 3rem);
|
||||
}
|
||||
.accept-button, .reject-button {
|
||||
font-size: .8rem;
|
||||
padding: .6rem;
|
||||
margin: auto;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
border: 0;
|
||||
font-weight: 800;
|
||||
alignment: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.accept-button {
|
||||
background-color: rgba(61, 148, 61, 0.91);
|
||||
}
|
||||
.reject-button {
|
||||
background-color: rgba(176, 64, 64, 0.61);
|
||||
}
|
||||
.accept-button:hover {
|
||||
background-color: rgb(61, 148, 61);
|
||||
}
|
||||
.reject-button:hover {
|
||||
background-color: rgb(176, 64, 64);
|
||||
}
|
||||
</style>
|
||||
223
imports/ui/Admin/Staff.svelte
Normal file
223
imports/ui/Admin/Staff.svelte
Normal file
@@ -0,0 +1,223 @@
|
||||
<script>
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import {onMount} from "svelte";
|
||||
import {Sites} from "../../api/sites";
|
||||
import GridTable from "./../GridTable.svelte";
|
||||
import {writable} from "svelte/store";
|
||||
import TextField from '@smui/textfield';
|
||||
import HelperText from '@smui/textfield/helper-text';
|
||||
import {Staff} from "../../api/staff";
|
||||
import Select, { Option } from '@smui/select';
|
||||
import Dialog, { Title, Content, Actions } from '@smui/dialog';
|
||||
import Button, { Label } from '@smui/button';
|
||||
|
||||
onMount(async () => {
|
||||
Meteor.subscribe('sites');
|
||||
});
|
||||
|
||||
// Load the sites (reactive).
|
||||
let sites = Sites.find({});
|
||||
let selectedSiteId;
|
||||
|
||||
let staff = null;
|
||||
let staffCount = 0;
|
||||
$: {
|
||||
if(selectedSiteId) {
|
||||
Meteor.subscribe('staff', selectedSiteId);
|
||||
staff = Staff.find({siteId: selectedSiteId});
|
||||
}
|
||||
}
|
||||
$: {
|
||||
if(staff) {
|
||||
staffCount = $staff.length;
|
||||
}
|
||||
else {
|
||||
staffCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let uploadType = 'CSV'
|
||||
|
||||
const uploadStaff = () => {
|
||||
// console.log(files);
|
||||
// console.log(selectedSite);
|
||||
// console.log(selectedSite._id);
|
||||
if(files && files.length) {
|
||||
let file = files[0];
|
||||
let reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
// console.log("Sending Data");
|
||||
// console.log(selectedSite._id);
|
||||
// console.log(reader.result);
|
||||
Meteor.call('staff.loadCsv', reader.result, uploadType, selectedSiteId);
|
||||
}
|
||||
reader.readAsText(file, "UTF-8");
|
||||
}
|
||||
}
|
||||
let files;
|
||||
|
||||
const staffColumns = [
|
||||
{
|
||||
key: "id",
|
||||
title: "ID",
|
||||
value: v => v.id,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "id",
|
||||
}, {
|
||||
key: "email",
|
||||
title: "Email",
|
||||
value: v => v.email,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "email",
|
||||
}, {
|
||||
key: "firstName",
|
||||
title: "First Name",
|
||||
value: v => v.firstName,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "firstName",
|
||||
}, {
|
||||
key: "lastName",
|
||||
title: "Last Name",
|
||||
value: v => v.lastName,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "lastName",
|
||||
},
|
||||
];
|
||||
const staffActions = {
|
||||
title: "Actions",
|
||||
headerWidgets: [
|
||||
{icon: "add_box", action: () => {editedStaff.set({});}, tooltip: "Add a staff member."}
|
||||
],
|
||||
rowWidgets: [
|
||||
{icon: "add_circle", action: (v) => {editedStaff.set(v)}},
|
||||
{icon: "delete", action: (v) => {deleteStaff(v)}}
|
||||
],
|
||||
};
|
||||
let editedStaff = writable(null);
|
||||
let siteSelectComponent;
|
||||
const onStaffSelection = (e) => {
|
||||
|
||||
}
|
||||
let dirtyStaff;
|
||||
// Copy the edited value when ever it changes, set some defaults for a new value object (to make the view happy).
|
||||
editedStaff.subscribe(v => {dirtyStaff = Object.assign({email: "", firstName: "", lastName: ""}, v)});
|
||||
const deleteStaff = staff => {
|
||||
//TODO:
|
||||
};
|
||||
const applyStaffChanges = () => {
|
||||
if(dirtyStaff._id)
|
||||
Meteor.call("staff.update", dirtyStaff);
|
||||
else
|
||||
Meteor.call("staff.add", dirtyStaff);
|
||||
editedStaff.set(null);
|
||||
dirtyStaff = null;
|
||||
}
|
||||
const rejectStaffChanges = () => {
|
||||
editedStaff.set(null);
|
||||
}
|
||||
let openExportHelpDialog = false;
|
||||
const clickOpenExportHelpDialog = () => {
|
||||
openExportHelpDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<Select bind:value={selectedSiteId} label="Site" bind:this={siteSelectComponent}>
|
||||
{#each $sites as site}
|
||||
<Option value={site._id}>{site.name}</Option>
|
||||
{/each}
|
||||
</Select>
|
||||
|
||||
<h2>Site Staff ({staffCount})</h2>
|
||||
<form on:submit|preventDefault={uploadStaff}>
|
||||
<input style="display: inline-block" type="file" multiple="false" accept="text/*" bind:files={files}/>
|
||||
<select bind:value={uploadType}>
|
||||
<option value="CSV">Comma Separated Value (CSV)</option>
|
||||
<option value="Aeries Text Report">Aeries Text Report (TXT)</option>
|
||||
</select>
|
||||
<input disabled="{!selectedSiteId}" type="submit" value="Upload"/>
|
||||
<input type="button" value="?" on:click={clickOpenExportHelpDialog}/>
|
||||
</form>
|
||||
<GridTable bind:rows={staff} columns="{staffColumns}" actions="{staffActions}" rowKey="{(v) => {return v._id}}" bind:edited="{editedStaff}" on:selection={onStaffSelection}>
|
||||
{#if dirtyStaff}
|
||||
<div class="editorContainer">
|
||||
<div style="grid-column: 1/span 1">
|
||||
<TextField type="text" style="width: 100%" bind:value={dirtyStaff.email} label="Email">
|
||||
</TextField>
|
||||
</div>
|
||||
<div style="grid-column: 1/span 1">
|
||||
<TextField type="text" style="width: 100%" bind:value={dirtyStaff.firstName} label="First Name">
|
||||
</TextField>
|
||||
</div>
|
||||
<div style="grid-column: 1/span 1">
|
||||
<TextField type="text" style="width: 100%" bind:value={dirtyStaff.lastName} label="Last Name">
|
||||
</TextField>
|
||||
</div>
|
||||
|
||||
<button type="button" style="grid-column: 2/span 1;" class="button accept-button material-icons material-symbols-outlined" on:click={applyStaffChanges}>
|
||||
check
|
||||
</button>
|
||||
<button type="button" style="grid-column: 3/span 1;" class="button reject-button material-icons material-symbols-outlined" on:click={rejectStaffChanges}>
|
||||
close
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</GridTable>
|
||||
<Dialog bind:open={openExportHelpDialog} aria-labelledby="exportHelpTitle" aria-describedby="exportHelpContent" surface$style="width: 800px; max-width: calc(100vw - 32px);">
|
||||
<Title id="exportHelpTitle">Exporting Staff Data</Title>
|
||||
<Content id="exportHelpContent">
|
||||
<h3>Aeries</h3>
|
||||
<p>For the Aeries system, log into your Aeries web interface and navigate to the query page. Enter the following query (change PSC = 5 to your school's number):</p>
|
||||
<pre style="font-size: 0.7rem"><code>LIST STF ID FN LN EM BY FN IF PSC = 5 AND TG # I</code></pre>
|
||||
<p>Run the query and validate that all staff have an email address and a staff ID. The `TG # I` hides staff that are inactive.</p>
|
||||
<p>You have two options for export. You can:</p>
|
||||
<ol class="help">
|
||||
<li class="help">Click the `Report` button to generate a TXT formatted report. Use Single Spacing, Automatic Orientation and no page breaks. The generated text is CSV but with a repeating collection of headers for each page. The headers will be ignored.</li>
|
||||
<li class="help">Click the `Excel` button to generate an excel spreadsheet of the results. Open this with any spreadsheet software (Excel, LibreOffice Calc, or Google Sheets) and save as CSV. The first line in the CSV file is assumed to be a header and will be ignored.</li>
|
||||
</ol>
|
||||
<p>Now upload the generated CSV file (or text file).</p>
|
||||
</Content>
|
||||
<Actions>
|
||||
<Button action="accept">
|
||||
<Label>Done</Label>
|
||||
</Button>
|
||||
</Actions>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
.editorContainer {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(10px, 1fr) minmax(3rem, 3rem) minmax(3rem, 3rem);
|
||||
}
|
||||
.accept-button, .reject-button {
|
||||
font-size: .8rem;
|
||||
padding: .6rem;
|
||||
margin: auto;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
border: 0;
|
||||
font-weight: 800;
|
||||
alignment: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.accept-button {
|
||||
background-color: rgba(61, 148, 61, 0.91);
|
||||
}
|
||||
.reject-button {
|
||||
background-color: rgba(176, 64, 64, 0.61);
|
||||
}
|
||||
.accept-button:hover {
|
||||
background-color: rgb(61, 148, 61);
|
||||
}
|
||||
.reject-button:hover {
|
||||
background-color: rgb(176, 64, 64);
|
||||
}
|
||||
</style>
|
||||
236
imports/ui/Admin/Students.svelte
Normal file
236
imports/ui/Admin/Students.svelte
Normal file
@@ -0,0 +1,236 @@
|
||||
<script>
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import {onMount} from "svelte";
|
||||
import {Sites} from "../../api/sites";
|
||||
import GridTable from "./../GridTable.svelte";
|
||||
import {writable} from "svelte/store";
|
||||
import TextField from '@smui/textfield';
|
||||
import HelperText from '@smui/textfield/helper-text';
|
||||
import {Students} from "../../api/students";
|
||||
import Select, { Option } from '@smui/select';
|
||||
import Dialog, { Title, Content, Actions } from '@smui/dialog';
|
||||
import Button, { Label } from '@smui/button';
|
||||
|
||||
onMount(async () => {
|
||||
Meteor.subscribe('sites');
|
||||
});
|
||||
|
||||
// Load the sites (reactive).
|
||||
let sites = Sites.find({});
|
||||
let selectedSiteId;
|
||||
|
||||
let students;
|
||||
let studentCount = 0;
|
||||
$: {
|
||||
if(selectedSiteId) {
|
||||
Meteor.subscribe('students', selectedSiteId);
|
||||
students = Students.find({siteId: selectedSiteId});
|
||||
}
|
||||
}
|
||||
$: {
|
||||
if(students) {
|
||||
studentCount = $students.length;
|
||||
}
|
||||
else {
|
||||
studentCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let uploadType = 'CSV'
|
||||
|
||||
const uploadStudents = () => {
|
||||
// console.log(files);
|
||||
// console.log(selectedSite);
|
||||
// console.log(selectedSite._id);
|
||||
if(files && files.length) {
|
||||
let file = files[0];
|
||||
let reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
// console.log("Sending Data");
|
||||
// console.log(selectedSite._id);
|
||||
// console.log(reader.result);
|
||||
Meteor.call('students.loadCsv', reader.result, uploadType, selectedSiteId);
|
||||
}
|
||||
reader.readAsText(file, "UTF-8");
|
||||
}
|
||||
}
|
||||
let files;
|
||||
|
||||
const studentColumns = [
|
||||
{
|
||||
key: "email",
|
||||
title: "Email",
|
||||
value: v => v.email,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "email",
|
||||
}, {
|
||||
key: "firstName",
|
||||
title: "First Name",
|
||||
value: v => v.firstName,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "firstName",
|
||||
}, {
|
||||
key: "lastName",
|
||||
title: "Last Name",
|
||||
value: v => v.lastName,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "lastName",
|
||||
}, {
|
||||
key: "grade",
|
||||
title: "Grade",
|
||||
value: v => v.grade,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "grade",
|
||||
},
|
||||
];
|
||||
const studentsActions = {
|
||||
title: "Actions",
|
||||
headerWidgets: [
|
||||
{icon: "add_box", action: () => {editedStudent.set({});}, tooltip: "Add a student."}
|
||||
],
|
||||
rowWidgets: [
|
||||
{icon: "add_circle", action: (v) => {editedStudent.set(v)}},
|
||||
{icon: "delete", action: (v) => {deleteStudent(v)}}
|
||||
],
|
||||
};
|
||||
let siteSelectComponent;
|
||||
let editedStudent = writable(null);
|
||||
const onStudentSelection = (e) => {
|
||||
}
|
||||
let dirtyStudent;
|
||||
// Copy the edited value when ever it changes, set some defaults for a new value object (to make the view happy).
|
||||
editedStudent.subscribe(v => {dirtyStudent = Object.assign({email: "", firstName: "", lastName: "", grade: ""}, v)});
|
||||
const deleteStudent = student => {
|
||||
//TODO:
|
||||
};
|
||||
const applyStudentChanges = () => {
|
||||
if(dirtyStudent._id)
|
||||
Meteor.call("students.update", dirtyStudent);
|
||||
else
|
||||
Meteor.call("students.add", dirtyStudent);
|
||||
editedStudent.set(null);
|
||||
dirtyStudent = null;
|
||||
}
|
||||
const rejectStudentChanges = () => {
|
||||
editedStudent.set(null);
|
||||
}
|
||||
let openExportHelpDialog = false;
|
||||
const clickOpenExportHelpDialog = () => {
|
||||
openExportHelpDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<Select bind:value={selectedSiteId} label="Site" bind:this={siteSelectComponent}>
|
||||
{#each $sites as site}
|
||||
<Option value={site._id}>{site.name}</Option>
|
||||
{/each}
|
||||
</Select>
|
||||
<h2>Site Students ({studentCount})</h2>
|
||||
<form on:submit|preventDefault={uploadStudents}>
|
||||
<input style="display: inline-block" type="file" multiple="false" accept="text/*" bind:files={files}/>
|
||||
<select bind:value={uploadType}>
|
||||
<option value="CSV">Comma Separated Value (CSV)</option>
|
||||
<option value="Aeries Text Report">Aeries Text Report (TXT)</option>
|
||||
</select>
|
||||
<input disabled="{!selectedSiteId}" type="submit" value="Upload"/>
|
||||
<input type="button" value="?" on:click={clickOpenExportHelpDialog}/>
|
||||
</form>
|
||||
<GridTable bind:rows={students} columns="{studentColumns}" actions="{studentsActions}" rowKey="{(v) => {return v._id}}" bind:edited="{editedStudent}" on:selection={onStudentSelection}>
|
||||
{#if dirtyStudent}
|
||||
<div class="editorContainer">
|
||||
<div style="grid-column: 1/span 1">
|
||||
<TextField type="text" style="width: 100%" bind:value={dirtyStudent.email} label="Email">
|
||||
</TextField>
|
||||
</div>
|
||||
<div style="grid-column: 1/span 1">
|
||||
<TextField type="text" style="width: 100%" bind:value={dirtyStudent.firstName} label="First Name">
|
||||
</TextField>
|
||||
</div>
|
||||
<div style="grid-column: 1/span 1">
|
||||
<TextField type="text" style="width: 100%" bind:value={dirtyStudent.lastName} label="Last Name">
|
||||
</TextField>
|
||||
</div>
|
||||
<div style="grid-column: 1/span 1">
|
||||
<TextField type="text" style="width: 100%" bind:value={dirtyStudent.grade} label="Grade">
|
||||
</TextField>
|
||||
</div>
|
||||
|
||||
<button type="button" style="grid-column: 2/span 1;" class="button accept-button material-icons material-symbols-outlined" on:click={applyStudentChanges}>
|
||||
check
|
||||
</button>
|
||||
<button type="button" style="grid-column: 3/span 1;" class="button reject-button material-icons material-symbols-outlined" on:click={rejectStudentChanges}>
|
||||
close
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</GridTable>
|
||||
|
||||
<Dialog bind:open={openExportHelpDialog} aria-labelledby="exportHelpTitle" aria-describedby="exportHelpContent" surface$style="width: 800px; max-width: calc(100vw - 32px);">
|
||||
<Title id="exportHelpTitle">Exporting Student Data</Title>
|
||||
<Content id="exportHelpContent">
|
||||
<h3>Aeries</h3>
|
||||
<p>For the Aeries system, log into your Aeries web interface and navigate to the query page. Enter the following query:</p>
|
||||
<pre style="font-size: 0.7rem"><code>LIST STU STU.ID STU.SEM STU.FN STU.LN STU.NG BY STU.ID STU.NG STU.SEM IF STU.NG >= 7 AND NG <= 12 AND STU.NS = 5</code></pre>
|
||||
<p>Run the query and validate that all students have an email address and a student ID. You likely also want to check that the student `next grade (NG)` field is set correctly for the students.</p>
|
||||
<p>You have two options for export. You can:</p>
|
||||
<ol class="help">
|
||||
<li class="help">Click the `Report` button to generate a TXT formatted report. Use Single Spacing, Automatic Orientation and no page breaks. The generated text is CSV but with a repeating collection of headers for each page. The headers will be ignored.</li>
|
||||
<li class="help">Click the `Excel` button to generate an excel spreadsheet of the results. Open this with any spreadsheet software (Excel, LibreOffice Calc, or Google Sheets) and save as CSV. The first line in the CSV file is assumed to be a header and will be ignored.</li>
|
||||
</ol>
|
||||
<p>Now upload the generated CSV file (or text file).</p>
|
||||
</Content>
|
||||
<Actions>
|
||||
<Button action="accept">
|
||||
<Label>Done</Label>
|
||||
</Button>
|
||||
</Actions>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
ol.help {
|
||||
list-style-type: decimal;
|
||||
margin-top: 0.2rem;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
li.help {
|
||||
list-style: inherit;
|
||||
padding-top: 0.2rem;
|
||||
padding-bottom: 0.2rem;
|
||||
}
|
||||
.editorContainer {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(10px, 1fr) minmax(3rem, 3rem) minmax(3rem, 3rem);
|
||||
}
|
||||
.accept-button, .reject-button {
|
||||
font-size: .8rem;
|
||||
padding: .6rem;
|
||||
margin: auto;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
border: 0;
|
||||
font-weight: 800;
|
||||
alignment: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.accept-button {
|
||||
background-color: rgba(61, 148, 61, 0.91);
|
||||
}
|
||||
.reject-button {
|
||||
background-color: rgba(176, 64, 64, 0.61);
|
||||
}
|
||||
.accept-button:hover {
|
||||
background-color: rgb(61, 148, 61);
|
||||
}
|
||||
.reject-button:hover {
|
||||
background-color: rgb(176, 64, 64);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user