Fixed a number of bugs; added a few fields.

This commit is contained in:
2022-08-08 22:15:55 -07:00
parent 4560d7203d
commit c96b4a6901
10 changed files with 3791 additions and 160 deletions

View File

@@ -60,12 +60,14 @@ Meteor.methods({
check(assigneeType, String); check(assigneeType, String);
check(assetId, String); check(assetId, String);
if(assigneeType !== 'Student' || assigneeType !== 'Staff') {
if(assigneeType !== 'Student' && assigneeType !== 'Staff') {
// Should never happen. // Should never happen.
console.error("Error: Received incorrect assignee type in adding an assignment."); console.error("Error: Received incorrect assignee type in adding an assignment.");
console.error(assigneeType);
} }
else if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) { else if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
AssetAssignments.insert({assetId, assigneeType: assigneeType === "Student" ? 0 : 1, assigneeId}); AssetAssignments.insert({assetId, assigneeType, assigneeId});
} }
}, },
'AssetAssignments.remove'(_id) { 'AssetAssignments.remove'(_id) {

View File

@@ -42,25 +42,27 @@ if (Meteor.isServer) {
// This code only runs on the server // This code only runs on the server
Meteor.publish('assetTypes', function() { Meteor.publish('assetTypes', function() {
return AssetTypes.find({}, {sort: {name: 1}}); return AssetTypes.find({});
}); });
} }
Meteor.methods({ Meteor.methods({
'assetTypes.add'(name, description) { 'assetTypes.add'(name, description, year) {
check(name, String); check(name, String);
check(description, String); check(description, String);
check(year, String);
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) { if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
AssetTypes.insert({name, description}); AssetTypes.insert({name, description, year});
} }
}, },
'assetTypes.update'(_id, name, description) { 'assetTypes.update'(_id, name, description, year) {
check(_id, String); check(_id, String);
check(name, String); check(name, String);
check(description, String); check(description, String);
check(year, String);
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) { if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
AssetTypes.update({_id}, {$set: {name, description}}); AssetTypes.update({_id}, {$set: {name, description, year}});
} }
}, },
'assetTypes.remove'(_id) { 'assetTypes.remove'(_id) {

View File

@@ -13,6 +13,13 @@
const assetTypesColumns = [ const assetTypesColumns = [
{ {
key: "year",
title: "Year",
value: v => v.year,
minWidth: 100,
weight: 1,
cls: "year",
}, {
key: "name", key: "name",
title: "Name", title: "Name",
value: v => v.name, value: v => v.name,
@@ -43,17 +50,17 @@
} }
let dirtyAssetType; let dirtyAssetType;
// Copy the edited value when ever it changes, set some defaults for a new value object (to make the view happy). // 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)}); editedAssetType.subscribe(v => {dirtyAssetType = Object.assign({name: "", description: "", year: ""}, v)});
// Load the sites (reactive). // Load the sites (reactive).
let assetTypes = AssetTypes.find({}); let assetTypes = AssetTypes.find({}, {sort: {year: -1}});
const deleteAssetType = assetType => { const deleteAssetType = assetType => {
//TODO: //TODO:
}; };
const applyAssetTypeChanges = () => { const applyAssetTypeChanges = () => {
if(dirtyAssetType._id) if(dirtyAssetType._id)
Meteor.call("assetTypes.update", dirtyAssetType._id, dirtyAssetType.name, dirtyAssetType.description); Meteor.call("assetTypes.update", dirtyAssetType._id, dirtyAssetType.name, dirtyAssetType.description, dirtyAssetType.year);
else else
Meteor.call("assetTypes.add", dirtyAssetType.name, dirtyAssetType.description); Meteor.call("assetTypes.add", dirtyAssetType.name, dirtyAssetType.description, dirtyAssetType.year);
editedAssetType.set(null); editedAssetType.set(null);
dirtyAssetType = null; dirtyAssetType = null;
} }
@@ -68,6 +75,9 @@
{#if dirtyAssetType} {#if dirtyAssetType}
<div class="editorContainer"> <div class="editorContainer">
<div style="grid-column: 1/span 1"> <div style="grid-column: 1/span 1">
<TextField type="text" style="width: 100%" bind:value={dirtyAssetType.year} label="Year">
<HelperText slot="helper">The manufacture or purchase year. Used for sorting assets so most recent ones appear on top.</HelperText>
</TextField>
<TextField type="text" style="width: 100%" bind:value={dirtyAssetType.name} label="Name"> <TextField type="text" style="width: 100%" bind:value={dirtyAssetType.name} label="Name">
<HelperText slot="helper">Provide a unique name for the asset type.</HelperText> <HelperText slot="helper">Provide a unique name for the asset type.</HelperText>
</TextField> </TextField>

View File

@@ -4,9 +4,9 @@
import {Meteor} from "meteor/meteor"; import {Meteor} from "meteor/meteor";
import {onMount} from "svelte"; import {onMount} from "svelte";
import AssetList from "/imports/ui/Assets/AssetList.svelte"; import AssetList from "/imports/ui/Assets/AssetList.svelte";
import AssetDataEntry from "/imports/ui/Assets/AssetDataEntry.svelte"; import AddAssets from "/imports/ui/Assets/AddAssets.svelte";
import {useTracker} from "meteor/rdb:svelte-meteor-data"; import {useTracker} from "meteor/rdb:svelte-meteor-data";
import Assign from "/imports/ui/Assets/Assign.svelte"; import AssignAssets from "/imports/ui/Assets/AssignAssets.svelte";
import Assignments from "/imports/ui/Assets/Assignments.svelte"; import Assignments from "/imports/ui/Assets/Assignments.svelte";
let canManageLaptops = false; let canManageLaptops = false;
@@ -23,12 +23,12 @@
let tabs = []; let tabs = [];
if(canManageLaptops) { if(canManageLaptops) {
tabs.push({id: 'assignments', label: 'Assignments'}); tabs.push({id: 'listAssignments', label: 'Assignment List'});
tabs.push({id: 'assignment', label: 'Assign'}); tabs.push({id: 'assignAssets', label: 'Assign Assets'});
} }
if(isAdmin) { if(isAdmin) {
tabs.push({id: 'list', label: 'Asset List'}); tabs.push({id: 'listAssets', label: 'Asset List'});
tabs.push({id: 'entry', label: 'Data Entry'}); tabs.push({id: 'addAssets', label: 'Add Assets'});
} }
let activeTab = tabs[0]; let activeTab = tabs[0];
</script> </script>
@@ -39,13 +39,13 @@
<Label>{tab.label}</Label> <Label>{tab.label}</Label>
</Tab> </Tab>
</TabBar> </TabBar>
{#if activeTab && activeTab.id === 'list'} {#if activeTab && activeTab.id === 'listAssets'}
<AssetList></AssetList> <AssetList></AssetList>
{:else if activeTab && activeTab.id === 'entry'} {:else if activeTab && activeTab.id === 'addAssets'}
<AssetDataEntry></AssetDataEntry> <AddAssets></AddAssets>
{:else if activeTab && activeTab.id === 'assignment'} {:else if activeTab && activeTab.id === 'assignAssets'}
<Assign></Assign> <AssignAssets></AssignAssets>
{:else if activeTab && activeTab.id === 'assignments'} {:else if activeTab && activeTab.id === 'listAssignments'}
<Assignments></Assignments> <Assignments></Assignments>
{/if} {/if}
</div> </div>

View File

@@ -9,6 +9,7 @@
import TextField from '@smui/textfield'; import TextField from '@smui/textfield';
import u from 'umbrellajs'; import u from 'umbrellajs';
import {AssetTypes} from "/imports/api/asset-types"; import {AssetTypes} from "/imports/api/asset-types";
import Paper from '@smui/paper';
onMount(async () => { onMount(async () => {
Meteor.subscribe('assetTypes'); Meteor.subscribe('assetTypes');
@@ -17,7 +18,7 @@
let assetTypes; let assetTypes;
//$m: assetTypes = AssetTypes.find({}).fetch(); //$m: assetTypes = AssetTypes.find({}).fetch();
//$: assetTypes = useTracker(() => AssetTypes.find({}).fetch()); //$: assetTypes = useTracker(() => AssetTypes.find({}).fetch());
$: assetTypes = AssetTypes.find({}); $: assetTypes = AssetTypes.find({}, {sort: {year: -1}});
let selectedAssetTypes = []; let selectedAssetTypes = [];
let selectedType = null; let selectedType = null;
@@ -116,17 +117,19 @@
</Actions> </Actions>
</Dialog> </Dialog>
<h3 style="display: inline-block">Asset Types</h3> <h3 style="display: block">Asset Types</h3>
<Button class="addBtn" on:click={openAssetTypesDialog}> <Button class="addBtn" on:click={openAssetTypesDialog}>
<Label>Add...</Label> <Label>Add...</Label>
</Button> </Button>
<List class="assetTypeList" singleSelection dense> <Paper>
{#each selectedAssetTypes as type} <List class="assetTypeList" singleSelection dense>
<Item on:SMUI:action={() => (selectedAssetType = type)} selected={selectedAssetType === type}> {#each selectedAssetTypes as type}
<Text>{type.name}</Text> <Item on:SMUI:action={() => (selectedAssetType = type)} selected={selectedAssetType === type}>
</Item> <Text>{type.name}</Text>
{/each} </Item>
</List> {/each}
</List>
</Paper>
<div style="grid-column: 1/span 1"> <div style="grid-column: 1/span 1">
<TextField id="assetIdField" type="text" style="width: 100%" bind:value={assetId} label="AssetId"> <TextField id="assetIdField" type="text" style="width: 100%" bind:value={assetId} label="AssetId">
</TextField> </TextField>
@@ -147,7 +150,7 @@
} }
:global(.addBtn) { :global(.addBtn) {
margin-left: 4rem; /*margin-left: 4rem;*/
} }
:global(.assetTypeList) { :global(.assetTypeList) {

View File

@@ -1,7 +1,7 @@
<script> <script>
import {Meteor} from "meteor/meteor"; import {Meteor} from "meteor/meteor";
import {onMount} from "svelte"; import {onMount} from "svelte";
import {writable} from "svelte/store"; import {writable, derived} 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 Select, { Option } from '@smui/select'; import Select, { Option } from '@smui/select';
@@ -16,7 +16,12 @@
Meteor.subscribe('assets'); Meteor.subscribe('assets');
}); });
let assetTypes; let assetTypes;
$: assetTypes = AssetTypes.find({}); $: assetTypes = AssetTypes.find({}, {sort: {year: -1}});
$: assetTypeNameMap = derived(assetTypes, $assetTypes => $assetTypes.reduce((map, obj) => {
map[obj._id] = obj.name;
return map;
}, {}));
// Asset Table // // Asset Table //
@@ -35,6 +40,17 @@
minWidth: 100, minWidth: 100,
weight: 1, weight: 1,
cls: "serial", cls: "serial",
}, {
key: "asset",
title: "Asset Type",
value: v => {
$: {
return v.assetTypeId ? $assetTypeNameMap[v.assetTypeId] : "-"
}
},
minWidth: 100,
weight: 1,
cls: "serial",
}, },
]; ];
const assetActions = { const assetActions = {

View File

@@ -58,6 +58,7 @@
const createAssignment = () => { const createAssignment = () => {
if(assetId && assetId.length && selectedAssignee) { if(assetId && assetId.length && selectedAssignee) {
Meteor.call("AssetAssignments.add", assetId, selectedCategory === 'Student' ? "Student" : "Staff", selectedAssignee._id) Meteor.call("AssetAssignments.add", assetId, selectedCategory === 'Student' ? "Student" : "Staff", selectedAssignee._id)
// TODO: Set focus to the asset ID field.
assetId = ""; assetId = "";
} }
} }
@@ -105,7 +106,7 @@
<TextField bind:this={assetIdWidget} style="flex-grow: 999;" type="text" bind:value={assetId} label="Asset ID"> <TextField bind:this={assetIdWidget} style="flex-grow: 999;" type="text" bind:value={assetId} label="Asset ID">
</TextField> </TextField>
<Button variant="raised" color="secondary" on:click={createAssignment()} disabled={!assetId || assetId.length === 0 || !selectedAssignee}> <Button variant="raised" color="secondary" on:click={createAssignment} disabled={!assetId || assetId.length === 0 || !selectedAssignee}>
<Label style="color: white">Create</Label> <Label style="color: white">Create</Label>
</Button> </Button>
</div> </div>

View File

@@ -2,14 +2,9 @@
import {Meteor} from "meteor/meteor"; import {Meteor} from "meteor/meteor";
import {onMount} from "svelte"; import {onMount} from "svelte";
import {Sites} from "../../api/sites"; import {Sites} from "../../api/sites";
import GridTable from "./../GridTable.svelte";
import {writable} from "svelte/store";
import TextField from '@smui/textfield'; import TextField from '@smui/textfield';
import HelperText from '@smui/textfield/helper-text';
import {Students} from "../../api/students"; import {Students} from "../../api/students";
import Select, { Option } from '@smui/select'; import Select, { Option } from '@smui/select';
import Dialog, { Title, Content, Actions } from '@smui/dialog';
import Button, { Label } from '@smui/button';
import {Staff} from "/imports/api/staff"; import {Staff} from "/imports/api/staff";
import List, {Item, Graphic, Meta, Text, PrimaryText, SecondaryText} from '@smui/list'; import List, {Item, Graphic, Meta, Text, PrimaryText, SecondaryText} from '@smui/list';
import Paper from '@smui/paper'; import Paper from '@smui/paper';
@@ -30,12 +25,14 @@
let selectedSiteId; let selectedSiteId;
let categories = ['Email', 'First Name', 'Last Name']; let categories = ['Email', 'First Name', 'Last Name'];
let selectedCategory = 'Email'; let selectedCategory = 'Email';
let selectedGrade = 'All'; let selectedGrade = 'All Grades';
let searchText = ""; let searchText = "";
let searchResults = []; let searchResults;
let selectedResult; let selectedResult;
$: { $: {
console.log("Site ID")
console.log(selectedSiteId)
if(selectedSiteId) { if(selectedSiteId) {
Meteor.subscribe('students', selectedSiteId); Meteor.subscribe('students', selectedSiteId);
Meteor.subscribe('staff', selectedSiteId); Meteor.subscribe('staff', selectedSiteId);
@@ -44,34 +41,37 @@
$: { $: {
selectedResult = null; selectedResult = null;
console.log("Starting search")
// Require at least two characters in the search field before we start filtering. // Require at least two characters in the search field before we start filtering.
if(selectedSiteId && selectedGrade && selectedCategory && searchText && searchText.length > 1) { if(selectedSiteId && selectedGrade && selectedCategory) {
let query = {}; let query = {};
if(selectedCategory === 'Email') { if(searchText && searchText.length > 0) {
query.email = {$regex: searchText, $options: 'i'}; if (selectedCategory === 'Email') {
} query.email = {$regex: searchText, $options: 'i'};
else if(selectedCategory === 'First Name') { } else if (selectedCategory === 'First Name') {
query.firstName = {$regex: searchText, $options: 'i'}; query.firstName = {$regex: searchText, $options: 'i'};
} } else {
else { query.lastName = {$regex: searchText, $options: 'i'};
query.lastName = {$regex: searchText, $options: 'i'}; }
} }
if(selectedCategory === "Staff") { if(selectedCategory === "Staff") {
searchResults = Staff.find(query); searchResults = Staff.find(query);
} }
else { else {
if(selectedGrade !== 'All') { if(selectedGrade !== 'All Grades') {
query.grade = selectedGrade; query.grade = selectedGrade;
} }
searchResults = Students.find(query).fetch(); console.log("Searching")
console.log(query)
searchResults = Students.find(query);
} }
} }
else { else {
searchResults = []; searchResults = undefined;
} }
} }
</script> </script>
@@ -117,7 +117,8 @@
<!-- </Button>--> <!-- </Button>-->
</div> </div>
<List twoLine singleSelection> <List twoLine singleSelection>
{#each searchResults as result} {#if searchResults}
{#each $searchResults as result}
<Item selected={selectedResult === result}> <Item selected={selectedResult === result}>
<Text> <Text>
<PrimaryText>{result.firstName} {result.lastName}</PrimaryText> <PrimaryText>{result.firstName} {result.lastName}</PrimaryText>
@@ -125,6 +126,7 @@
</Text> </Text>
</Item> </Item>
{/each} {/each}
{/if}
</List> </List>
</div> </div>

3795
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,9 @@
"private": true, "private": true,
"scripts": { "scripts": {
"start": "meteor run", "start": "meteor run",
"build": "npm install --product && meteor build --architecture os.linux.x86_64 --server-only ../", "update": "npx browserslist@latest --update-db",
"build": "npm install --omit=dev && meteor build --architecture os.linux.x86_64 --server-only ../",
"build_old": "npm install --product && meteor build --architecture os.linux.x86_64 --server-only ../",
"test": "meteor test --once --driver-package meteortesting:mocha", "test": "meteor test --once --driver-package meteortesting:mocha",
"test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha", "test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
"visualize": "meteor --production --extra-packages bundle-visualizer", "visualize": "meteor --production --extra-packages bundle-visualizer",
@@ -21,6 +23,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",
"smui-theme": "^6.0.0-beta.16",
"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",
@@ -61,7 +64,6 @@
"@smui/textfield": "^6.0.0-beta.16", "@smui/textfield": "^6.0.0-beta.16",
"chai": "^4.2.0", "chai": "^4.2.0",
"rollup-plugin-css-only": "^3.1.0", "rollup-plugin-css-only": "^3.1.0",
"smui-theme": "^6.0.0-beta.16",
"typescript": "^4.7.4" "typescript": "^4.7.4"
} }
} }