Updated the models.

This commit is contained in:
2022-06-20 08:24:35 -07:00
parent f5e98bee03
commit 6c6778d58e
14 changed files with 661 additions and 3186 deletions

View File

@@ -7,7 +7,7 @@
meteor-base@1.5.1 # Packages every Meteor app needs to have meteor-base@1.5.1 # Packages every Meteor app needs to have
mobile-experience@1.1.0 # Packages for a great mobile UX mobile-experience@1.1.0 # Packages for a great mobile UX
mongo@1.15.0 # The database Meteor supports right now mongo@1.15.0 # The database Meteor supports right now
jquery # Wrapper package for npm-installed jquery jquery # Wrapper package for npm-installed jquery
reactive-var@1.0.11 # Reactive variable for tracker reactive-var@1.0.11 # Reactive variable for tracker
tracker@1.2.0 # Meteor's client-side reactive programming library tracker@1.2.0 # Meteor's client-side reactive programming library
@@ -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

View File

@@ -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

View 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.
}
},
});

View 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
View 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.
}
},
});

View 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);

View File

@@ -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";

View File

@@ -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});
} }
} }

View File

@@ -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.
} }
}, },
}); });

View File

@@ -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*/);
let email = values[0];
let id = values[1];
let firstName = values[2];
let lastName = values[3];
let grade = values[4];
let firstNameAlias = values[5];
let lastNameAlias = values[6];
let student = {email, id, firstName, lastName, grade, firstNameAlias, lastNameAlias};
console.log(student); if(values.length === 7) {
let email = values[0];
let id = values[1];
let firstName = values[2];
let lastName = values[3];
let grade = parseInt(values[4], 10);
let firstNameAlias = values[5];
let active = true;
let student = {siteId, email, id, firstName, lastName, grade, firstNameAlias, active};
// 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.

View File

@@ -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);

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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",