Updated the models.
This commit is contained in:
@@ -18,6 +18,9 @@ ecmascript@0.16.2 # Enable ECMAScript2015+ syntax in app code
|
|||||||
typescript@4.5.4 # Enable TypeScript syntax in .ts and .tsx modules
|
typescript@4.5.4 # Enable TypeScript syntax in .ts and .tsx modules
|
||||||
shell-server@0.5.0 # Server-side component of the `meteor shell` command
|
shell-server@0.5.0 # Server-side component of the `meteor shell` command
|
||||||
|
|
||||||
|
aldeed:collection2 # Attaches a schema to a collection
|
||||||
|
aldeed:schema-index # Allows the schema to specify fields to be indexed
|
||||||
|
|
||||||
svelte:compiler
|
svelte:compiler
|
||||||
#static-html@1.3.2
|
#static-html@1.3.2
|
||||||
rdb:svelte-meteor-data
|
rdb:svelte-meteor-data
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ accounts-password@2.3.1
|
|||||||
accounts-ui@1.4.2
|
accounts-ui@1.4.2
|
||||||
accounts-ui-unstyled@1.7.0
|
accounts-ui-unstyled@1.7.0
|
||||||
alanning:roles@3.4.0
|
alanning:roles@3.4.0
|
||||||
|
aldeed:collection2@2.10.0
|
||||||
|
aldeed:collection2-core@1.2.0
|
||||||
|
aldeed:schema-deny@1.1.0
|
||||||
|
aldeed:schema-index@1.1.1
|
||||||
|
aldeed:simple-schema@1.5.4
|
||||||
allow-deny@1.1.1
|
allow-deny@1.1.1
|
||||||
autoupdate@1.8.0
|
autoupdate@1.8.0
|
||||||
babel-compiler@7.9.0
|
babel-compiler@7.9.0
|
||||||
@@ -47,6 +52,7 @@ launch-screen@1.3.0
|
|||||||
less@4.0.0
|
less@4.0.0
|
||||||
localstorage@1.2.0
|
localstorage@1.2.0
|
||||||
logging@1.3.1
|
logging@1.3.1
|
||||||
|
mdg:validation-error@0.2.0
|
||||||
meteor@1.10.0
|
meteor@1.10.0
|
||||||
meteor-base@1.5.1
|
meteor-base@1.5.1
|
||||||
meteortesting:browser-tests@1.3.5
|
meteortesting:browser-tests@1.3.5
|
||||||
@@ -72,6 +78,7 @@ oauth2@1.3.1
|
|||||||
observe-sequence@1.0.20
|
observe-sequence@1.0.20
|
||||||
ordered-dict@1.1.0
|
ordered-dict@1.1.0
|
||||||
promise@0.12.0
|
promise@0.12.0
|
||||||
|
raix:eventemitter@0.1.3
|
||||||
random@1.2.0
|
random@1.2.0
|
||||||
rate-limit@1.0.9
|
rate-limit@1.0.9
|
||||||
rdb:svelte-meteor-data@1.0.0
|
rdb:svelte-meteor-data@1.0.0
|
||||||
|
|||||||
68
imports/api/asset-assignments.js
Normal file
68
imports/api/asset-assignments.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import {Mongo} from "meteor/mongo";
|
||||||
|
import {Meteor} from "meteor/meteor";
|
||||||
|
import { check } from 'meteor/check';
|
||||||
|
import { Roles } from 'meteor/alanning:roles';
|
||||||
|
import SimpleSchema from "simpl-schema";
|
||||||
|
import {AssetTypes} from "./asset-types";
|
||||||
|
|
||||||
|
export const AssetAssignments = new Mongo.Collection('assetAssignments');
|
||||||
|
/*
|
||||||
|
const TYPE_STUDENT = 1;
|
||||||
|
const TYPE_STAFF = 2;
|
||||||
|
|
||||||
|
const AssetAssignmentsSchema = new SimpleSchema({
|
||||||
|
assetId: {
|
||||||
|
type: String,
|
||||||
|
label: "Asset ID",
|
||||||
|
optional: false,
|
||||||
|
index: 1,
|
||||||
|
unique: false
|
||||||
|
},
|
||||||
|
assigneeId: {
|
||||||
|
type: String,
|
||||||
|
label: "Assignee ID",
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
assigneeType: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
label: "Assignee Type",
|
||||||
|
optional: false,
|
||||||
|
min: 1,
|
||||||
|
max: 2,
|
||||||
|
exclusiveMin: false,
|
||||||
|
exclusiveMax: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
AssetAssignments.attachSchema(AssetAssignmentsSchema);
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
// Drop any old indexes we no longer will use. Create indexes we need.
|
||||||
|
//try {AssetTypes._dropIndex("name")} catch(e) {}
|
||||||
|
//AssetTypes.createIndex({name: "text"}, {name: "name", unique: false});
|
||||||
|
AssetTypes.createIndex({assetId: 1}, {name: "AssetID", unique: false});
|
||||||
|
|
||||||
|
// This code only runs on the server
|
||||||
|
Meteor.publish('assetAssignments', function() {
|
||||||
|
return AssetAssignments.find({});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Meteor.methods({
|
||||||
|
'assetAssignments.add'(assetId) {
|
||||||
|
// check(assetTypeId, String);
|
||||||
|
// check(assetId, String);
|
||||||
|
|
||||||
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
|
// Assets.insert({assetTypeId, assetId});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'assetAssignments.remove'(_id) {
|
||||||
|
check(_id, String);
|
||||||
|
|
||||||
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
|
//TODO: Need to first verify there are no checked out assets to the staff member.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
72
imports/api/asset-types.js
Normal file
72
imports/api/asset-types.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import {Mongo} from "meteor/mongo";
|
||||||
|
import {Meteor} from "meteor/meteor";
|
||||||
|
import { check } from 'meteor/check';
|
||||||
|
import { Roles } from 'meteor/alanning:roles';
|
||||||
|
import SimpleSchema from "simpl-schema";
|
||||||
|
|
||||||
|
//
|
||||||
|
// An asset type is a specific type of equipment. Example: Lenovo 100e Chromebook.
|
||||||
|
//
|
||||||
|
export const AssetTypes = new Mongo.Collection('assetType');
|
||||||
|
/*
|
||||||
|
|
||||||
|
const AssetTypesSchema = new SimpleSchema({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
label: "Model Name",
|
||||||
|
optional: false,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
label: "Description",
|
||||||
|
optional: true,
|
||||||
|
trim: true,
|
||||||
|
defaultValue: ""
|
||||||
|
},
|
||||||
|
hasSerial: {
|
||||||
|
type: Boolean,
|
||||||
|
label: "Is a serial number available for all instances?",
|
||||||
|
optional: false,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
AssetTypes.attachSchema(AssetTypesSchema);
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
// Drop any old indexes we no longer will use. Create indexes we need.
|
||||||
|
try {AssetTypes._dropIndex("name")} catch(e) {}
|
||||||
|
//AssetTypes.createIndex({name: "text"}, {name: "name", unique: false});
|
||||||
|
AssetTypes.createIndex({id: 1}, {name: "External ID", unique: true});
|
||||||
|
|
||||||
|
//Debug: Show all indexes.
|
||||||
|
// AssetTypes.rawCollection().indexes((err, indexes) => {
|
||||||
|
// console.log(indexes);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// This code only runs on the server
|
||||||
|
Meteor.publish('assetTypes', function() {
|
||||||
|
return AssetTypes.find({}, {sort: {name: 1}});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Meteor.methods({
|
||||||
|
'assetTypes.add'(name, description, hasSerial) {
|
||||||
|
check(name, String);
|
||||||
|
check(description, String);
|
||||||
|
check(hasSerial, Boolean);
|
||||||
|
|
||||||
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
|
AssetTypes.insert({name, description, hasSerial});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'assetTypes.remove'(_id) {
|
||||||
|
check(_id, String);
|
||||||
|
|
||||||
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
|
//TODO: Need to first verify there are no checked out assets to the staff member.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
80
imports/api/assets.js
Normal file
80
imports/api/assets.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import {Mongo} from "meteor/mongo";
|
||||||
|
import {Meteor} from "meteor/meteor";
|
||||||
|
import { check } from 'meteor/check';
|
||||||
|
import { Roles } from 'meteor/alanning:roles';
|
||||||
|
import SimpleSchema from "simpl-schema";
|
||||||
|
import {AssetTypes} from "./asset-types";
|
||||||
|
|
||||||
|
export const Assets = new Mongo.Collection('assets');
|
||||||
|
|
||||||
|
/*
|
||||||
|
const AssetsSchema = new SimpleSchema({
|
||||||
|
assetTypeId: {
|
||||||
|
type: String,
|
||||||
|
label: "Asset Type ID",
|
||||||
|
optional: false,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
assetId: {
|
||||||
|
type: String,
|
||||||
|
label: "Asset ID",
|
||||||
|
optional: false,
|
||||||
|
trim: true,
|
||||||
|
index: 1,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
serial: {
|
||||||
|
type: String,
|
||||||
|
label: "Serial",
|
||||||
|
optional: true,
|
||||||
|
trim: false,
|
||||||
|
index: 1,
|
||||||
|
unique: false
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Assets.attachSchema(AssetsSchema);
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
// Drop any old indexes we no longer will use. Create indexes we need.
|
||||||
|
//try {Assets._dropIndex("serial")} catch(e) {}
|
||||||
|
Assets.createIndex({assetId: 1}, {name: "AssetID", unique: true});
|
||||||
|
Assets.createIndex({serial: 1}, {name: "Serial", unique: false});
|
||||||
|
|
||||||
|
// This code only runs on the server
|
||||||
|
Meteor.publish('assets', function() {
|
||||||
|
return Assets.find({});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Meteor.methods({
|
||||||
|
'assets.add'(assetTypeId, assetId, serial) {
|
||||||
|
check(assetTypeId, String);
|
||||||
|
check(assetId, String);
|
||||||
|
check(serial, String);
|
||||||
|
|
||||||
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
|
let assetType = AssetTypes.findOne({assetTypeId});
|
||||||
|
|
||||||
|
if(assetType.hasSerial && serial || !assetType.hasSerial && !serial) {
|
||||||
|
if(serial) {
|
||||||
|
Assets.insert({assetTypeId, assetId, serial});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Assets.insert({assetTypeId, assetId});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//Should never get here due to client side validation.
|
||||||
|
console.log("Error: Must provide a serial for asset types marked as having one, and may not provide one for asset types not marked as having one.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'assets.remove'(_id) {
|
||||||
|
check(_id, String);
|
||||||
|
|
||||||
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
|
//TODO: Need to first verify there are no checked out assets to the staff member.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
93
imports/api/example-schema.js
Normal file
93
imports/api/example-schema.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import {AssetTypes} from "./asset-types";
|
||||||
|
import {SimpleSchema} from "simpl-schema/dist/SimpleSchema";
|
||||||
|
|
||||||
|
const AssetTypesSchema = new SimpleSchema({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
label: "Name",
|
||||||
|
optional: false,
|
||||||
|
trim: true,
|
||||||
|
index: 1,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
tags: { //An array of ProductTag names. Note that we are not using the ProductTag ID's because I want a looser connection (if a ProductTag is deleted, it isn't a big deal if it isn't maintained in the Product records).
|
||||||
|
type: [String],
|
||||||
|
label: "Tags",
|
||||||
|
optional: false,
|
||||||
|
defaultValue: []
|
||||||
|
},
|
||||||
|
measures: { //A JSON array of Measure ID's.
|
||||||
|
type: Array,
|
||||||
|
label: "Measures",
|
||||||
|
optional: false,
|
||||||
|
defaultValue: []
|
||||||
|
},
|
||||||
|
'measures.$': {
|
||||||
|
type: String,
|
||||||
|
label: "Measure ID",
|
||||||
|
regEx: SimpleSchema.RegEx.Id
|
||||||
|
},
|
||||||
|
aliases: { //A JSON array of alternate names.
|
||||||
|
type: Array,
|
||||||
|
label: "Aliases",
|
||||||
|
optional: false,
|
||||||
|
defaultValue: []
|
||||||
|
},
|
||||||
|
'aliases.$': {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
label: "Created On",
|
||||||
|
optional: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: Date,
|
||||||
|
label: "Updated On",
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
deactivated: { //This is turned on first, if true it will hide the product in production views, but keep the product available in the sale views. It is intended to be turned on for products that are no longer produced, but for which we have remaining inventory.
|
||||||
|
type: Boolean,
|
||||||
|
label: "Deactivated",
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
hidden: { //Deactivated must first be true. Hides the product everywhere in the system except in historical pages. The inventory should be all sold prior to hiding a product.
|
||||||
|
type: Boolean,
|
||||||
|
label: "Hidden",
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
timestamp: {
|
||||||
|
type: Date,
|
||||||
|
label: "Timestamp",
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
weekOfYear: {
|
||||||
|
type: Number,
|
||||||
|
label: "Week Of Year",
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
type: Number,
|
||||||
|
label: "Amount",
|
||||||
|
optional: false,
|
||||||
|
decimal: true
|
||||||
|
},
|
||||||
|
price: {
|
||||||
|
type: Number,
|
||||||
|
label: "Price",
|
||||||
|
optional: false,
|
||||||
|
min: 0,
|
||||||
|
exclusiveMin: true,
|
||||||
|
},
|
||||||
|
assigneeType: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
label: "Assignee Type",
|
||||||
|
optional: false,
|
||||||
|
min: 1,
|
||||||
|
max: 2,
|
||||||
|
exclusiveMin: false,
|
||||||
|
exclusiveMax: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
AssetTypes.attachSchema(AssetTypesSchema);
|
||||||
@@ -2,5 +2,8 @@ import "./users.js";
|
|||||||
import "./data-collection.js";
|
import "./data-collection.js";
|
||||||
import "./admin.js";
|
import "./admin.js";
|
||||||
import "./students.js";
|
import "./students.js";
|
||||||
import "./rooms.js";
|
import "./staff.js";
|
||||||
import "./sites.js";
|
import "./sites.js";
|
||||||
|
import "./asset-types.js";
|
||||||
|
import "./assets.js";
|
||||||
|
import "./asset-assignments.js";
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import {Mongo} from "meteor/mongo";
|
import {Mongo} from "meteor/mongo";
|
||||||
import {Meteor} from "meteor/meteor";
|
import {Meteor} from "meteor/meteor";
|
||||||
import {Students} from "./students";
|
import {Students} from "./students";
|
||||||
import {Rooms} from "./rooms";
|
import {Staff} from "./staff";
|
||||||
|
import { Roles } from 'meteor/alanning:roles';
|
||||||
|
|
||||||
export const Sites = new Mongo.Collection('sites');
|
export const Sites = new Mongo.Collection('sites');
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ Meteor.methods({
|
|||||||
if(site) {
|
if(site) {
|
||||||
//Clear any site references in student/room entries.
|
//Clear any site references in student/room entries.
|
||||||
Students.update({siteId: _id}, {$unset: {siteId: 1}});
|
Students.update({siteId: _id}, {$unset: {siteId: 1}});
|
||||||
Rooms.update({siteId: _id}, {$unset: {siteId: 1}});
|
Staff.update({siteId: _id}, {$unset: {siteId: 1}});
|
||||||
Sites.remove({_id});
|
Sites.remove({_id});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
import {Mongo} from "meteor/mongo";
|
import {Mongo} from "meteor/mongo";
|
||||||
import {Meteor} from "meteor/meteor";
|
import {Meteor} from "meteor/meteor";
|
||||||
|
import { Roles } from 'meteor/alanning:roles';
|
||||||
|
|
||||||
export const Rooms = new Mongo.Collection('rooms');
|
export const Staff = new Mongo.Collection('staff');
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
// This code only runs on the server
|
// This code only runs on the server
|
||||||
Meteor.publish('rooms', function(siteId) {
|
Meteor.publish('staff', function(siteId) {
|
||||||
return Rooms.find({siteId});
|
return Staff.find({siteId});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
'rooms.add'(name, siteId) {
|
'staff.add'(firstName, lastName, email, siteId) {
|
||||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
Rooms.insert({name, siteId});
|
Staff.insert({firstName, lastName, email, siteId});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'rooms.remove'(_id) {
|
'staff.remove'(_id) {
|
||||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
//TODO: Need to first verify there are no checked out assets to the room.
|
//TODO: Need to first verify there are no checked out assets to the staff member.
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -2,10 +2,13 @@ import { Meteor } from 'meteor/meteor';
|
|||||||
import { Mongo } from 'meteor/mongo';
|
import { Mongo } from 'meteor/mongo';
|
||||||
import { check } from 'meteor/check';
|
import { check } from 'meteor/check';
|
||||||
import {Sites} from "./sites";
|
import {Sites} from "./sites";
|
||||||
|
import { Roles } from 'meteor/alanning:roles';
|
||||||
|
|
||||||
export const Students = new Mongo.Collection('students');
|
export const Students = new Mongo.Collection('students');
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
|
Students.createIndex({id: 1}, {name: "External ID", unique: true});
|
||||||
|
|
||||||
// This code only runs on the server
|
// This code only runs on the server
|
||||||
Meteor.publish('students', function(siteId) {
|
Meteor.publish('students', function(siteId) {
|
||||||
return Students.find({siteId});
|
return Students.find({siteId});
|
||||||
@@ -13,6 +16,19 @@ if (Meteor.isServer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
|
/**
|
||||||
|
* Sets a first name alias that can be overridden by the one that is imported.
|
||||||
|
* @param _id The student's database ID.
|
||||||
|
* @param alias The alias to set for the student.
|
||||||
|
*/
|
||||||
|
'students.setAlias'(_id, alias) {
|
||||||
|
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||||
|
check(_id, String);
|
||||||
|
check(alias, String);
|
||||||
|
|
||||||
|
Students.update({_id}, !alias || !alias.length() ? {$unset: {alias: true}} : {$set: {alias}});
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Expects the CSV string to contain comma delimited data in the form:
|
* Expects the CSV string to contain comma delimited data in the form:
|
||||||
* email, student ID, first name, last name, grade, first name alias, last name alias
|
* email, student ID, first name, last name, grade, first name alias, last name alias
|
||||||
@@ -32,7 +48,7 @@ Meteor.methods({
|
|||||||
check(csv, String);
|
check(csv, String);
|
||||||
check(siteId, String);
|
check(siteId, String);
|
||||||
|
|
||||||
let site = Sites.find({_id: siteId});
|
let site = Sites.findOne({_id: siteId});
|
||||||
|
|
||||||
if(site) {
|
if(site) {
|
||||||
let lines = csv.split(/\r?\n/);
|
let lines = csv.split(/\r?\n/);
|
||||||
@@ -45,16 +61,21 @@ Meteor.methods({
|
|||||||
skip = 2;
|
skip = 2;
|
||||||
} else {
|
} else {
|
||||||
let values = line.split(/\s*,\s*/);
|
let values = line.split(/\s*,\s*/);
|
||||||
|
|
||||||
|
if(values.length === 7) {
|
||||||
let email = values[0];
|
let email = values[0];
|
||||||
let id = values[1];
|
let id = values[1];
|
||||||
let firstName = values[2];
|
let firstName = values[2];
|
||||||
let lastName = values[3];
|
let lastName = values[3];
|
||||||
let grade = values[4];
|
let grade = parseInt(values[4], 10);
|
||||||
let firstNameAlias = values[5];
|
let firstNameAlias = values[5];
|
||||||
let lastNameAlias = values[6];
|
let active = true;
|
||||||
let student = {email, id, firstName, lastName, grade, firstNameAlias, lastNameAlias};
|
let student = {siteId, email, id, firstName, lastName, grade, firstNameAlias, active};
|
||||||
|
|
||||||
console.log(student);
|
// console.log(student);
|
||||||
|
// Update or insert in the db.
|
||||||
|
Students.upsert({id}, {$set: student});
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: Find existing student. Update student, and move them to the new site.
|
//TODO: Find existing student. Update student, and move them to the new site.
|
||||||
//TODO: Create student if none exists.
|
//TODO: Create student if none exists.
|
||||||
|
|||||||
@@ -6,18 +6,17 @@
|
|||||||
import {writable} from "svelte/store";
|
import {writable} from "svelte/store";
|
||||||
import TextField from '@smui/textfield';
|
import TextField from '@smui/textfield';
|
||||||
import HelperText from '@smui/textfield/helper-text';
|
import HelperText from '@smui/textfield/helper-text';
|
||||||
|
import {Students} from "../api/students";
|
||||||
|
import {Staff} from "../api/staff";
|
||||||
|
import {AssetTypes} from "../api/asset-types";
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
Meteor.subscribe('sites');
|
Meteor.subscribe('sites');
|
||||||
|
Meteor.subscribe('assetTypes');
|
||||||
});
|
});
|
||||||
|
|
||||||
const fixRecords = () => {Meteor.call("admin.fixRecords");}
|
const fixRecords = () => {Meteor.call("admin.fixRecords");}
|
||||||
|
|
||||||
const submitForm = () => {
|
|
||||||
Meteor.call('students.loadCsv');
|
|
||||||
}
|
|
||||||
let files;
|
|
||||||
|
|
||||||
const siteColumns = [
|
const siteColumns = [
|
||||||
{
|
{
|
||||||
key: "_id",
|
key: "_id",
|
||||||
@@ -66,10 +65,162 @@
|
|||||||
const rejectSiteChanges = () => {
|
const rejectSiteChanges = () => {
|
||||||
editedSite.set(null);
|
editedSite.set(null);
|
||||||
}
|
}
|
||||||
|
let selectedSite = null;
|
||||||
|
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 onSiteSelection = (e) => {
|
||||||
|
selectedSite = Sites.findOne({_id: e.detail});
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
}, {
|
||||||
|
key: "hasSerial",
|
||||||
|
title: "Has Serial",
|
||||||
|
value: v => v.hasSerial,
|
||||||
|
minWidth: 100,
|
||||||
|
weight: 1,
|
||||||
|
cls: "hasSerial",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let editedAssetType = writable(null);
|
||||||
|
const onAssetTypeSelection = (e) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
let dirtyAssetType = null;
|
||||||
|
// Copy the edited site when ever it changes, set some defaults for a new site object (to make the view happy).
|
||||||
|
editedAssetType.subscribe(v => {dirtyAssetType = Object.assign({name: ""}, v)});
|
||||||
|
// Load the sites (reactive).
|
||||||
|
let assetTypes = AssetTypes.find({});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<GridTable bind:rows={sites} columns="{siteColumns}" actions="{actions}" rowKey="{(user) => {return user._id}}" bind:edited="{editedSite}">
|
<GridTable bind:rows={sites} columns="{siteColumns}" actions="{actions}" rowKey="{(v) => {return v._id}}" bind:edited="{editedSite}" on:selection={onSiteSelection}>
|
||||||
{#if dirtySite}
|
{#if dirtySite}
|
||||||
<div class="editorContainer">
|
<div class="editorContainer">
|
||||||
<div style="grid-column: 1/span 1">
|
<div style="grid-column: 1/span 1">
|
||||||
@@ -88,17 +239,81 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</GridTable>
|
</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>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<h2>Asset Types</h2>
|
||||||
|
<GridTable bind:rows={assetTypes} columns="{assetTypesColumns}" actions="{null}" rowKey="{(v) => {return v._id}}" bind:edited="{editedAssetType}" on:selection={onAssetTypeSelection}>
|
||||||
|
{#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>
|
||||||
<!--{#each sites as site}-->
|
<!--{#each sites as site}-->
|
||||||
<!-- <div>{site.name}</div>-->
|
<!-- <div>{site.name}</div>-->
|
||||||
<!--{/each}-->
|
<!--{/each}-->
|
||||||
<!-- <button type="button" on:click={fixRecords}>Fix Records</button>-->
|
<!-- <button type="button" on:click={fixRecords}>Fix Records</button>-->
|
||||||
<form on:submit={submitForm}>
|
|
||||||
<input type="file" bind:files={files}/>
|
|
||||||
<input type="submit"/>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
form {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
.editorContainer {
|
.editorContainer {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(10px, 1fr) minmax(3rem, 3rem) minmax(3rem, 3rem);
|
grid-template-columns: minmax(10px, 1fr) minmax(3rem, 3rem) minmax(3rem, 3rem);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let rows;
|
export let rows;
|
||||||
export let columns;
|
export let columns;
|
||||||
export let rowKey; //Must only be null/undefined if the row is a new object (not associated with a row in the table). Should not change.
|
export let rowKey; //Must only be null/undefined if the row is a new object (not associated with a row in the table). Should not change.
|
||||||
@@ -6,6 +8,8 @@
|
|||||||
export let actions;
|
export let actions;
|
||||||
export let selection; //TODO: allow multiple selections? Make selections optional?
|
export let selection; //TODO: allow multiple selections? Make selections optional?
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
// Setup a width for each column.
|
// Setup a width for each column.
|
||||||
columns.forEach(column => {
|
columns.forEach(column => {
|
||||||
let min = column.minWidth ? Math.max(10, column.minWidth) : 10;
|
let min = column.minWidth ? Math.max(10, column.minWidth) : 10;
|
||||||
@@ -72,6 +76,7 @@
|
|||||||
|
|
||||||
selectedRowElement = element;
|
selectedRowElement = element;
|
||||||
element.classList.add('selected');
|
element.classList.add('selected');
|
||||||
|
dispatch('selection', selectedRowElement.dataset.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edit row code.
|
// Edit row code.
|
||||||
|
|||||||
3215
package-lock.json
generated
3215
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,7 @@
|
|||||||
"meteor-node-stubs": "^1.0.0",
|
"meteor-node-stubs": "^1.0.0",
|
||||||
"moment": "^2.29.2",
|
"moment": "^2.29.2",
|
||||||
"mongodb": "^4.4.1",
|
"mongodb": "^4.4.1",
|
||||||
|
"simpl-schema": "^1.12.2",
|
||||||
"svelte": "^3.46.4",
|
"svelte": "^3.46.4",
|
||||||
"svelte-material-ui": "^6.0.0-beta.16",
|
"svelte-material-ui": "^6.0.0-beta.16",
|
||||||
"tinro": "^0.6.12",
|
"tinro": "^0.6.12",
|
||||||
|
|||||||
Reference in New Issue
Block a user