Updated meteor; Modified the assignments byPerson page considerably to improve the workflow; Added an external id to sites; Added an import for students; Improved the students page.
This commit is contained in:
@@ -9,6 +9,17 @@ import Select from '@mui/material/Select';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import {Students} from "/imports/api/students";
|
||||
import {Sites} from "/imports/api/sites";
|
||||
import Box from "@mui/material/Box";
|
||||
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
|
||||
import ToggleButton from "@mui/material/ToggleButton";
|
||||
import {InputLabel, List, ListItemButton, ListItemText, Switch} from "@mui/material";
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import Checkbox from "@mui/material/Checkbox";
|
||||
|
||||
const cssSitesSelect = {
|
||||
margin: '0.6rem 0',
|
||||
@@ -24,7 +35,7 @@ const cssFieldColumnContainer = {
|
||||
}
|
||||
const cssGridFieldContainer = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: "1fr 1fr 1fr",
|
||||
gridTemplateColumns: "1fr 1fr 1fr 1fr",
|
||||
columnGap: '1rem',
|
||||
rowGap: '0.4rem',
|
||||
marginBottom: '1.5rem'
|
||||
@@ -39,8 +50,10 @@ const StudentEditor = ({value, close, defaultSiteId}) => {
|
||||
const [email, setEmail] = useState(value.email || "")
|
||||
const [id, setId] = useState(value.id || "")
|
||||
const [firstName, setFirstName] = useState(value.firstName || "")
|
||||
const [firstNameAlias, setFirstNameAlias] = useState(value.firstNameAlias || "")
|
||||
const [lastName, setLastName] = useState(value.lastName || "")
|
||||
const [grade, setGrade] = useState(value.grade || "")
|
||||
const [active, setActive] = useState(value.active)
|
||||
const [siteId, setSiteId] = useState(value.siteId ? value.siteId : defaultSiteId)
|
||||
|
||||
const {sites} = useTracker(() => {
|
||||
@@ -57,9 +70,9 @@ const StudentEditor = ({value, close, defaultSiteId}) => {
|
||||
close()
|
||||
//TODO Should invert this and only close if there was success on the server.
|
||||
if(value._id)
|
||||
Meteor.call("students.update", value._id, id, firstName, lastName, email, siteId, grade);
|
||||
Meteor.call("students.update", value._id, id, firstName, firstNameAlias, lastName, email, siteId, grade, active);
|
||||
else
|
||||
Meteor.call("students.add", id, firstName, lastName, email, siteId, grade);
|
||||
Meteor.call("students.add", id, firstName, firstNameAlias, lastName, email, siteId, grade, active);
|
||||
}
|
||||
const rejectChanges = () => {
|
||||
close()
|
||||
@@ -72,7 +85,9 @@ const StudentEditor = ({value, close, defaultSiteId}) => {
|
||||
<TextField variant="standard" label="ID" value={id} onChange={(e) => {setId(e.target.value)}}/>
|
||||
<TextField variant="standard" label="Email" value={email} onChange={(e) => {setEmail(e.target.value)}}/>
|
||||
<TextField variant="standard" label="Grade" value={grade} onChange={(e) => {setGrade(e.target.value)}}/>
|
||||
<FormControlLabel control={<Switch variant="standard" checked={active} onChange={(e) => {setActive(e.target.checked)}}/>} label="Active"/>
|
||||
<TextField variant="standard" label="First Name" value={firstName} onChange={(e) => {setFirstName(e.target.value)}}/>
|
||||
<TextField variant="standard" label="Alias" value={firstNameAlias} onChange={(e) => {setFirstNameAlias(e.target.value)}}/>
|
||||
<TextField variant="standard" label="Last Name" value={lastName} onChange={(e) => {setLastName(e.target.value)}}/>
|
||||
<TextField select variant="standard" label="Site" value={siteId} onChange={(e) => {setSiteId(e.target.value)}}>
|
||||
{sites.map((next, i) => {
|
||||
@@ -103,8 +118,23 @@ export default () => {
|
||||
return {sites}
|
||||
});
|
||||
|
||||
const ACTIVE_BOTH = "both"
|
||||
const ACTIVE_ONLY = "active"
|
||||
const ACTIVE_OFF = "inactive"
|
||||
const [active, setActive] = useState(ACTIVE_BOTH)
|
||||
const [nameSearch, setNameSearch] = useState("")
|
||||
|
||||
const {students} = useTracker(() => {
|
||||
const studentQuery = site === siteAll._id ? {} : {siteId: site}
|
||||
|
||||
if(active !== ACTIVE_BOTH) {
|
||||
studentQuery["active"] = active === ACTIVE_ONLY
|
||||
}
|
||||
|
||||
if(nameSearch && nameSearch.length > 2) {
|
||||
studentQuery["$or"] = [{firstName: {$regex: nameSearch, $options: 'i'}}, {firstNameAlias: {$regex: nameSearch, $options: 'i'}}, {lastName: {$regex: nameSearch, $options: 'i'}}]
|
||||
}
|
||||
|
||||
let students = Students.find(studentQuery).fetch();
|
||||
|
||||
return {students}
|
||||
@@ -132,6 +162,10 @@ export default () => {
|
||||
name: "First Name",
|
||||
value: (row) => row.firstName,
|
||||
},
|
||||
{
|
||||
name: "Alias",
|
||||
value: (row) => row.firstNameAlias,
|
||||
},
|
||||
{
|
||||
name: "Last Name",
|
||||
value: (row) => row.lastName,
|
||||
@@ -149,6 +183,10 @@ export default () => {
|
||||
else return 1
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Active",
|
||||
value: (row) => row.active ? "Active" : "Inactive",
|
||||
},
|
||||
]
|
||||
|
||||
const options = {
|
||||
@@ -162,14 +200,81 @@ export default () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const importData = (type) => {
|
||||
let input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.onchange = _ => {
|
||||
let files = Array.from(input.files)
|
||||
|
||||
if(files.length === 1) {
|
||||
let reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
Meteor.call("students.loadCsv", reader.result, type, testImportOnly, (err, result) => {
|
||||
//Note: It would be nice to have feedback about the operation, but right now I cannot figure out how to wait on the server for the result of the callback that is wrapped by a bindEnvironment call.
|
||||
if(err) console.log(err)
|
||||
// else if(testImportOnly) console.log(result)
|
||||
})
|
||||
}
|
||||
reader.readAsText(input.files[0])
|
||||
}
|
||||
}
|
||||
input.click()
|
||||
}
|
||||
|
||||
const [showImportDialog, setShowImportDialog] = useState(false)
|
||||
const [testImportOnly, setTestImportOnly] = useState(false)
|
||||
|
||||
const openImportDialog = () => {
|
||||
setTestImportOnly(false)
|
||||
setShowImportDialog(true)
|
||||
}
|
||||
const closeImportDialog = (cause, importType) => {
|
||||
if(importType) importData(importType)
|
||||
|
||||
if(showImportDialog) setShowImportDialog(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextField label="Site" style={cssSitesSelect} select variant="standard" value={site} onChange={(e)=>{setSite(e.target.value)}}>
|
||||
{sites.map((next, i) => {
|
||||
return <MenuItem key={next._id} value={next._id}>{next.name}</MenuItem>
|
||||
})}
|
||||
</TextField>
|
||||
<Dialog open={showImportDialog}>
|
||||
<DialogTitle>Import</DialogTitle>
|
||||
<DialogContent style={{display: 'flex', flexDirection: 'column'}}>
|
||||
<p>Imports students for the entire district and deals with altering the "active" flag for students no longer in the district or who have graduated.</p>
|
||||
<h3>Middle of Year</h3>
|
||||
<pre>LIST STU SC ID SEM FN LN GR FNA</pre>
|
||||
<p>Run any time during the year to capture changes in student body.</p>
|
||||
<h3>End of Year</h3>
|
||||
<pre>LIST STU NS ID SEM FN LN NG FNA IF NG >= 12</pre>
|
||||
<p>Use this query for the import if the school year is over (before summer school). It utilizes the next school & next grade fields instead of the current grade/school.</p>
|
||||
<FormControlLabel control={<Checkbox checked={testImportOnly} onChange={(e) => setTestImportOnly(e.target.checked)}/>} label="Test Only"/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => closeImportDialog("button")}>Cancel</Button>
|
||||
<Button onClick={() => closeImportDialog("button", 'csv')}>Import CSV</Button>
|
||||
<Button onClick={() => closeImportDialog("button", 'aeries-txt')}>Import Aeries TXT</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Box component="div" sx={{m: 2, p: 2, border: '1px dashed grey'}}>
|
||||
<TextField label="Site" style={cssSitesSelect} select variant="standard" value={site} onChange={(e)=>{setSite(e.target.value)}}>
|
||||
{sites.map((next, i) => {
|
||||
return <MenuItem key={next._id} value={next._id}>{next.name}</MenuItem>
|
||||
})}
|
||||
</TextField>
|
||||
<Box component="div" sx={{display: "inline-block", marginLeft: "2rem"}}>
|
||||
<InputLabel htmlFor="activeGroup" sx={{fontSize: "0.8rem"}}>Active Students</InputLabel>
|
||||
<ToggleButtonGroup id="activeGroup" color="primary" value={active} exclusive onChange={(e)=>{setActive(e.target.value)}} aria-label="Active Students">
|
||||
<ToggleButton value={ACTIVE_BOTH}>All</ToggleButton>
|
||||
<ToggleButton value={ACTIVE_ONLY}>Active</ToggleButton>
|
||||
<ToggleButton value={ACTIVE_OFF}>Inactive</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
<TextField sx={{margin: "0.7rem 0 0 2rem"}} variant="standard" label="Name Search" value={nameSearch} onChange={(e) => {setNameSearch(e.target.value)}}/>
|
||||
<Box component="div" sx={{display: "inline-block", float: "right", marginTop: "1rem"}}>
|
||||
<Button variant="contained" color='secondary' className="button" onClick={openImportDialog}>Import</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<SimpleTable rows={students} columns={columns} options={options}/>
|
||||
</>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user