2022-09-07 08:58:00 -07:00
import { Meteor } from 'meteor/meteor' ;
import React , { useState } from 'react' ;
import { useTracker } from 'meteor/react-meteor-data' ;
import _ from 'lodash' ;
import SimpleTable from "/imports/ui/util/SimpleTable" ;
import TextField from "@mui/material/TextField" ;
import Button from "@mui/material/Button" ;
import Select from '@mui/material/Select' ;
import MenuItem from '@mui/material/MenuItem' ;
import { Students } from "/imports/api/students" ;
import { Sites } from "/imports/api/sites" ;
2023-06-16 11:52:48 -07:00
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" ;
2022-09-07 08:58:00 -07:00
const cssSitesSelect = {
margin : '0.6rem 0' ,
minWidth : '20rem' ,
}
const cssFieldColumnContainer = {
display : 'flex' ,
flexDirection : 'column' ,
backgroundColor : '#DDD' ,
padding : '0.5rem' ,
border : '1px solid #999' ,
borderRadius : '0.2rem'
}
const cssGridFieldContainer = {
display : 'grid' ,
2023-06-16 11:52:48 -07:00
gridTemplateColumns : "1fr 1fr 1fr 1fr" ,
2022-09-07 08:58:00 -07:00
columnGap : '1rem' ,
rowGap : '0.4rem' ,
marginBottom : '1.5rem'
}
const cssButtonContainer = {
display : 'flex' ,
gap : '1rem' ,
justifyContent : 'flex-end'
}
const StudentEditor = ( { value , close , defaultSiteId } ) => {
const [ email , setEmail ] = useState ( value . email || "" )
const [ id , setId ] = useState ( value . id || "" )
const [ firstName , setFirstName ] = useState ( value . firstName || "" )
2023-06-16 11:52:48 -07:00
const [ firstNameAlias , setFirstNameAlias ] = useState ( value . firstNameAlias || "" )
2022-09-07 08:58:00 -07:00
const [ lastName , setLastName ] = useState ( value . lastName || "" )
const [ grade , setGrade ] = useState ( value . grade || "" )
2023-06-16 11:52:48 -07:00
const [ active , setActive ] = useState ( value . active )
2022-09-07 08:58:00 -07:00
const [ siteId , setSiteId ] = useState ( value . siteId ? value . siteId : defaultSiteId )
const { sites } = useTracker ( ( ) => {
let sites = Sites . find ( { } ) . fetch ( ) ;
return { sites }
} ) ;
if ( ! siteId && sites && sites . length > 0 ) {
setSiteId ( sites [ 0 ] . _id )
}
const applyChanges = ( ) => {
close ( )
//TODO Should invert this and only close if there was success on the server.
if ( value . _id )
2023-06-16 11:52:48 -07:00
Meteor . call ( "students.update" , value . _id , id , firstName , firstNameAlias , lastName , email , siteId , grade , active ) ;
2022-09-07 08:58:00 -07:00
else
2023-06-16 11:52:48 -07:00
Meteor . call ( "students.add" , id , firstName , firstNameAlias , lastName , email , siteId , grade , active ) ;
2022-09-07 08:58:00 -07:00
}
const rejectChanges = ( ) => {
close ( )
}
return (
< div style = { cssFieldColumnContainer } >
< h1 > Student Editor < / h1 >
< div style = { cssGridFieldContainer } >
< 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 ) } } / >
2023-06-16 11:52:48 -07:00
< FormControlLabel control = { < Switch variant = "standard" checked = { active } onChange = { ( e ) => { setActive ( e . target . checked ) } } / > } label = "Active" / >
2022-09-07 08:58:00 -07:00
< TextField variant = "standard" label = "First Name" value = { firstName } onChange = { ( e ) => { setFirstName ( e . target . value ) } } / >
2023-06-16 11:52:48 -07:00
< TextField variant = "standard" label = "Alias" value = { firstNameAlias } onChange = { ( e ) => { setFirstNameAlias ( e . target . value ) } } / >
2022-09-07 08:58:00 -07:00
< 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 ) => {
return < MenuItem key = { next . _id } value = { next . _id } > { next . name } < / MenuItem >
} ) }
< / TextField >
< / div >
< div style = { cssButtonContainer } >
< Button variant = "contained" className = "button accept-button" onClick = { applyChanges } > Accept < / Button >
< Button variant = "outlined" className = "button reject-button" onClick = { rejectChanges } > Reject < / Button >
< / div >
< / div >
)
}
export default ( ) => {
const siteAll = { _id : 0 , name : "All" }
const [ site , setSite ] = useState ( siteAll . _id )
Meteor . subscribe ( 'sites' ) ;
Meteor . subscribe ( 'students' ) ;
const { sites } = useTracker ( ( ) => {
const sites = Sites . find ( { } ) . fetch ( ) ;
sites . push ( siteAll ) ;
return { sites }
} ) ;
2023-06-16 11:52:48 -07:00
const ACTIVE _BOTH = "both"
const ACTIVE _ONLY = "active"
const ACTIVE _OFF = "inactive"
const [ active , setActive ] = useState ( ACTIVE _BOTH )
const [ nameSearch , setNameSearch ] = useState ( "" )
2022-09-07 08:58:00 -07:00
const { students } = useTracker ( ( ) => {
const studentQuery = site === siteAll . _id ? { } : { siteId : site }
2023-06-16 11:52:48 -07:00
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' } } ]
}
2022-09-07 08:58:00 -07:00
let students = Students . find ( studentQuery ) . fetch ( ) ;
return { students }
} ) ;
const columns = [
{
name : "ID" ,
value : ( row ) => row . id ,
2022-09-10 17:42:38 -07:00
descendingComparator : ( a , b ) => {
if ( a . id === b . id ) return 0
else if ( ! a . id ) return - 1
else if ( ! b . id ) return 1
else if ( a . id . length < b . id . length ) return 1
else if ( a . id . length > b . id . length ) return - 1
else if ( b . id < a . id ) return - 1
else return 1
}
2022-09-07 08:58:00 -07:00
} ,
{
name : "Email" ,
value : ( row ) => row . email ,
} ,
{
name : "First Name" ,
value : ( row ) => row . firstName ,
} ,
2023-06-16 11:52:48 -07:00
{
name : "Alias" ,
value : ( row ) => row . firstNameAlias ,
} ,
2022-09-07 08:58:00 -07:00
{
name : "Last Name" ,
value : ( row ) => row . lastName ,
} ,
{
name : "GRD" ,
value : ( row ) => row . grade ,
2022-09-10 17:42:38 -07:00
descendingComparator : ( a , b ) => {
if ( a . grade === b . grade ) return 0
else if ( ! a . grade ) return - 1
else if ( ! b . grade ) return 1
else if ( a . grade . length < b . grade . length ) return 1
else if ( a . grade . length > b . grade . length ) return - 1
else if ( b . grade < a . grade ) return - 1
else return 1
}
2022-09-07 08:58:00 -07:00
} ,
2023-06-16 11:52:48 -07:00
{
name : "Active" ,
value : ( row ) => row . active ? "Active" : "Inactive" ,
} ,
2022-09-07 08:58:00 -07:00
]
const options = {
key : ( row ) => row . _id ,
editor : ( row , close ) => { return ( < StudentEditor value = { row } close = { close } defaultSiteId = { site } / > ) } ,
add : true ,
maxHeight : '40rem' ,
keyHandler : ( e , selected ) => {
if ( selected && selected . _id && e . key === "Delete" ) {
Meteor . call ( "students.remove" , selected . _id ) ;
}
}
}
2023-06-16 11:52:48 -07:00
const importData = ( type ) => {
let input = document . createElement ( 'input' )
input . type = 'file'
input . onchange = _ => {
let files = Array . from ( input . files )
2022-09-07 08:58:00 -07:00
2023-06-16 11:52:48 -07:00
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 )
}
2022-09-07 08:58:00 -07:00
return (
< >
2023-06-16 11:52:48 -07:00
< 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 & gt ; = 12 < / pre >
< p > Use this query for the import if the school year is over ( before summer school ) . It utilizes the next school & amp ; 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 >
2022-09-07 08:58:00 -07:00
< SimpleTable rows = { students } columns = { columns } options = { options } / >
< / >
)
}