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:
@@ -1,358 +1,30 @@
|
||||
<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 {Staff} from "../api/staff";
|
||||
import {AssetTypes} from "../api/asset-types";
|
||||
import Tab, { Label } from '@smui/tab';
|
||||
import TabBar from '@smui/tab-bar';
|
||||
import AssetTypes from "/imports/ui/Admin/AssetTypes.svelte";
|
||||
import Sites from "/imports/ui/Admin/Sites.svelte";
|
||||
import Students from "/imports/ui/Admin/Students.svelte";
|
||||
import Staff from "/imports/ui/Admin/Staff.svelte";
|
||||
|
||||
onMount(async () => {
|
||||
Meteor.subscribe('sites');
|
||||
Meteor.subscribe('assetTypes');
|
||||
});
|
||||
|
||||
// Should not be needed now. Did not work well - had a bug somewhere.
|
||||
// const fixRecords = () => {Meteor.call("admin.fixRecords");}
|
||||
|
||||
const siteColumns = [
|
||||
{
|
||||
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",
|
||||
},
|
||||
];
|
||||
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});
|
||||
}
|
||||
|
||||
let students = null;
|
||||
let staff = null;
|
||||
$: {
|
||||
if(selectedSite) {
|
||||
Meteor.subscribe('students', selectedSite._id);
|
||||
Meteor.subscribe('staff', selectedSite._id);
|
||||
students = Students.find({siteId: selectedSite._id});
|
||||
staff = Staff.find({siteId: selectedSite._id});
|
||||
}
|
||||
}
|
||||
|
||||
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, selectedSite._id);
|
||||
}
|
||||
reader.readAsText(file, "UTF-8");
|
||||
}
|
||||
}
|
||||
let files;
|
||||
|
||||
const studentColumns = [
|
||||
{
|
||||
key: "_id",
|
||||
title: "ID",
|
||||
value: v => v._id,
|
||||
minWidth: 20,
|
||||
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",
|
||||
}, {
|
||||
key: "grade",
|
||||
title: "Grade",
|
||||
value: v => v.grade,
|
||||
minWidth: 100,
|
||||
weight: 1,
|
||||
cls: "grade",
|
||||
},
|
||||
];
|
||||
let editedStudent = writable(null);
|
||||
const onStudentSelection = (e) => {
|
||||
|
||||
}
|
||||
|
||||
const staffColumns = [
|
||||
{
|
||||
key: "_id",
|
||||
title: "ID",
|
||||
value: v => v._id,
|
||||
minWidth: 20,
|
||||
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",
|
||||
},
|
||||
];
|
||||
let editedStaff = writable(null);
|
||||
const onStaffSelection = (e) => {
|
||||
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
editedSite.set(null);
|
||||
}
|
||||
let activeTab = null;
|
||||
</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>
|
||||
|
||||
{#if selectedSite}
|
||||
<h2>Site Students</h2>
|
||||
<form on:submit|preventDefault={uploadStudents}>
|
||||
<input style="display: inline-block" type="file" multiple="false" accept="text/csv" bind:files={files}/>
|
||||
<input type="submit" value="Upload"/>
|
||||
</form>
|
||||
<GridTable bind:rows={students} columns="{studentColumns}" actions="{null}" rowKey="{(v) => {return v._id}}" bind:edited="{editedStudent}" on:selection={onStudentSelection}>
|
||||
{#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>
|
||||
|
||||
<h2>Site Staff</h2>
|
||||
<GridTable bind:rows={staff} columns="{staffColumns}" actions="{null}" rowKey="{(v) => {return v._id}}" bind:edited="{editedStaff}" on:selection={onStaffSelection}>
|
||||
{#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>
|
||||
<TabBar tabs={[{id:'sites', label:'Sites'}, {id:'students', label:'Students'}, {id:'staff', label:'Staff'}, {id:'assetTypes', label:'Asset Types'}]} minWidth let:tab bind:active={activeTab}>
|
||||
<Tab {tab}>
|
||||
<Label>{tab.label}</Label>
|
||||
</Tab>
|
||||
</TabBar>
|
||||
{#if activeTab && activeTab.id === 'sites'}
|
||||
<Sites></Sites>
|
||||
{:else if activeTab && activeTab.id === 'students'}
|
||||
<Students></Students>
|
||||
{:else if activeTab && activeTab.id === 'staff'}
|
||||
<Staff></Staff>
|
||||
{:else if activeTab && activeTab.id === 'assetTypes'}
|
||||
<AssetTypes></AssetTypes>
|
||||
{/if}
|
||||
|
||||
<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>
|
||||
</style>
|
||||
Reference in New Issue
Block a user