├── webapp ├── .meteor │ ├── cordova-plugins │ ├── release │ ├── platforms │ ├── .gitignore │ ├── .finished-upgraders │ ├── .id │ ├── packages │ └── versions ├── .gitignore ├── client │ ├── app │ │ ├── workflows │ │ │ ├── calendarMonth │ │ │ │ ├── calendarMonth.less │ │ │ │ ├── calendarMonth.js │ │ │ │ └── calendarMonth.html │ │ │ ├── calendarYear │ │ │ │ ├── calendarYear.less │ │ │ │ ├── calendarYear.html │ │ │ │ └── calendarYear.js │ │ │ ├── reservationDetails │ │ │ │ ├── reservationDetails.less │ │ │ │ ├── reservationDetails.html │ │ │ │ └── reservationDetails.js │ │ │ ├── roomSelection │ │ │ │ ├── roomSelection.less │ │ │ │ ├── roomSelection.html │ │ │ │ └── roomSelection.js │ │ │ └── calendarDay │ │ │ │ ├── calendarDayTemplate.less │ │ │ │ ├── calendarDayTemplate.html │ │ │ │ └── calendarDayTemplate.js │ │ ├── app.resize.js │ │ ├── appContainer.js │ │ ├── appContainer.html │ │ ├── navbars │ │ │ ├── app.navbar.js │ │ │ └── app.navbar.html │ │ └── calendar.less │ ├── subscription.js │ └── syntax.custom.less ├── public │ └── scheduling-screenshot.png ├── model.js ├── server │ ├── publish.js │ └── initialize.schedule.js ├── tests │ ├── nightwatch │ │ └── walkthrough.js │ └── .reports │ │ └── nightwatch-acceptance │ │ └── FIREFOX_31.0_MAC_walkthrough.xml └── run_nightwatch.sh ├── .travis.yml └── readme.md /webapp/.meteor/cordova-plugins: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /webapp/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.0.3.1 2 | -------------------------------------------------------------------------------- /webapp/.gitignore: -------------------------------------------------------------------------------- 1 | .meteor/local 2 | .reports 3 | -------------------------------------------------------------------------------- /webapp/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /webapp/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | meteorite 3 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/calendarMonth/calendarMonth.less: -------------------------------------------------------------------------------- 1 | #calendarMonth{ 2 | 3 | } 4 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/calendarYear/calendarYear.less: -------------------------------------------------------------------------------- 1 | #calendarYear{ 2 | 3 | } 4 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/reservationDetails/reservationDetails.less: -------------------------------------------------------------------------------- 1 | #reservationDetails{ 2 | li{ 3 | height: 36px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /webapp/public/scheduling-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awatson1978/clinical-scheduling/HEAD/webapp/public/scheduling-screenshot.png -------------------------------------------------------------------------------- /webapp/model.js: -------------------------------------------------------------------------------- 1 | Rooms = new Meteor.Collection("rooms"); 2 | Schedule = new Meteor.Collection("schedule"); 3 | 4 | DowJonesSample = new Meteor.Collection("dowjones"); 5 | -------------------------------------------------------------------------------- /webapp/client/subscription.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('rooms'); 2 | 3 | Meteor.subscribe('schedule'); 4 | Meteor.subscribe('mySchedule',{}); 5 | 6 | Meteor.subscribe('dowjones'); 7 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/roomSelection/roomSelection.less: -------------------------------------------------------------------------------- 1 | .room{ 2 | cursor: pointer; 3 | } 4 | .room:hover{ 5 | background-color: lightgray !important; 6 | color: #333; 7 | } 8 | -------------------------------------------------------------------------------- /webapp/client/app/app.resize.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(window).resize(function(evt) { 4 | try{ 5 | Session.set("resized", new Date()); 6 | }catch(err){ 7 | console.log(err); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/calendarYear/calendarYear.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /webapp/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | -------------------------------------------------------------------------------- /webapp/client/app/appContainer.js: -------------------------------------------------------------------------------- 1 | Session.set('selectedRoom', ''); 2 | Session.set('display_date', moment().format("YYYY-MM-DD")); 3 | Session.set('selected_hour', ''); 4 | Session.set('selected_reservation_slot', ''); 5 | Session.set("resized", false); 6 | Session.set("last_reservation_change", ''); 7 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/calendarMonth/calendarMonth.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Template.calendarMonth.events({ 5 | "click #event": function(event, template){ 6 | 7 | } 8 | }); 9 | 10 | 11 | Template.calendarMonth.helpers({ 12 | options: function() { 13 | return { 14 | defaultView: 'month' 15 | }; 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /webapp/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 62mxmq1xce9yv1pj8ukw 8 | -------------------------------------------------------------------------------- /webapp/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # 3 | # 'meteor add' and 'meteor remove' will edit this file for you, 4 | # but you can also edit it by hand. 5 | 6 | standard-app-packages 7 | insecure 8 | less 9 | mrt:moment 10 | clinical:barcode 11 | d3js:d3 12 | mrt:bootstrap-3 13 | pfafman:font-awesome-4 14 | rzymek:fullcalendar 15 | clinical:nightwatch 16 | -------------------------------------------------------------------------------- /webapp/server/publish.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('mySchedule', function (ownerId) { 2 | return Schedule.find({owner: ownerId}, {limit: 40}); 3 | }); 4 | 5 | Meteor.publish('schedule', function () { 6 | return Schedule.find({},{limit: 8760}); 7 | }); 8 | Meteor.publish('rooms', function () { 9 | return Rooms.find(); 10 | }); 11 | 12 | Meteor.publish('dowjones', function () { 13 | return DowJonesSample.find(); 14 | }); 15 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/calendarMonth/calendarMonth.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/roomSelection/roomSelection.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /webapp/tests/nightwatch/walkthrough.js: -------------------------------------------------------------------------------- 1 | // tests/leaderboard.js 2 | module.exports = { 3 | "ActiveRecord::Table Walkthrough" : function (client) { 4 | client 5 | .url("http://localhost:3000") 6 | .waitForElementVisible('body', 1000) 7 | 8 | // make sure main elements are present 9 | .verify.elementPresent("#navbarHeader") 10 | .verify.elementPresent("#navbarHeaderNav") 11 | .verify.elementPresent("#mainPanel") 12 | .verify.elementPresent("#mainLayout") 13 | .verify.elementPresent("#navbarFooter") 14 | .verify.elementPresent("#navbarFooterNav") 15 | 16 | .end(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/roomSelection/roomSelection.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------- 2 | // room selection 3 | 4 | 5 | Template.roomSelection.helpers({ 6 | roomList: function(){ 7 | return Rooms.find({}, { 8 | sort: { 9 | name: 1 10 | } 11 | }); 12 | } 13 | }); 14 | 15 | 16 | // sets which room will be displayed in the scheduling calendar 17 | Template.roomSelection.events({ 18 | 'click .room': function() { 19 | console.log('click .room'); 20 | Session.set('selectedRoom', this._id); 21 | console.log('selectedRoom', Session.get('selectedRoom')); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/calendarDay/calendarDayTemplate.less: -------------------------------------------------------------------------------- 1 | .appointment-success{ 2 | background-color: #dff0d8; 3 | } 4 | .appointment-warning{ 5 | background-color: #faebcc; 6 | } 7 | .timeslot{ 8 | cursor: pointer; 9 | } 10 | .timeslot:hover{ 11 | background-color: lightgray !important; 12 | color: #333; 13 | } 14 | .table{ 15 | margin-bottom: 0px; 16 | } 17 | 18 | #previousButton{ 19 | width: 20%; 20 | left: 0px; 21 | } 22 | #nextButton{ 23 | width: 20%; 24 | right: 0px; 25 | } 26 | 27 | .clickable{ 28 | cursor: pointer; 29 | -moz-user-select: none; 30 | -khtml-user-select: none; 31 | -webkit-user-select: none; 32 | user-select: none; 33 | } 34 | .clickable:hover{ 35 | color: #333; 36 | } 37 | -------------------------------------------------------------------------------- /webapp/client/app/appContainer.html: -------------------------------------------------------------------------------- 1 | 2 | Clinical Scheduling 3 | 4 | 5 | 6 | {{> mainLayout}} 7 | 8 | 9 | 37 | -------------------------------------------------------------------------------- /webapp/client/app/navbars/app.navbar.js: -------------------------------------------------------------------------------- 1 | Template.navbarHeader.events({ 2 | 'click #navbarBrandLink':function(){ 3 | //$('#westPanel').sidebar('toggle'); 4 | toggleWestPanel(); 5 | }, 6 | 'click #keybindingsLink':function(){ 7 | $('#keybindingsModal').modal("show"); 8 | }, 9 | 'click #promptLink':function(){ 10 | $('#promptModal').modal("show"); 11 | }, 12 | 'click #confirmLink':function(){ 13 | $('#confirmModal').modal("show"); 14 | }, 15 | 'click #errorToggleLink':function(){ 16 | $('#errorPanel').sidebar('toggle'); 17 | }, 18 | 'click #eastPanelToggleLink':function(){ 19 | //$('#eastPanel').sidebar('toggle'); 20 | toggleEastPanel(); 21 | } 22 | 23 | }); 24 | 25 | 26 | Template.navbarFooter.events({ 27 | 'click #westPanelToggle':function(){ 28 | toggleWestPanel(); 29 | }, 30 | 'click #eastPanelToggle':function(){ 31 | toggleEastPanel(); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/reservationDetails/reservationDetails.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/calendarDay/calendarDayTemplate.html: -------------------------------------------------------------------------------- 1 | 2 | 33 | -------------------------------------------------------------------------------- /webapp/.meteor/versions: -------------------------------------------------------------------------------- 1 | application-configuration@1.0.4 2 | autoupdate@1.1.5 3 | base64@1.0.2 4 | binary-heap@1.0.2 5 | blaze@2.0.4 6 | blaze-tools@1.0.2 7 | boilerplate-generator@1.0.2 8 | callback-hook@1.0.2 9 | check@1.0.4 10 | clinical:barcode@2.0.2 11 | clinical:nightwatch@1.5.1 12 | d3js:d3@3.5.3 13 | ddp@1.0.14 14 | deps@1.0.6 15 | ejson@1.0.5 16 | fastclick@1.0.2 17 | follower-livedata@1.0.3 18 | geojson-utils@1.0.2 19 | html-tools@1.0.3 20 | htmljs@1.0.3 21 | http@1.0.10 22 | id-map@1.0.2 23 | insecure@1.0.2 24 | jquery@1.11.3 25 | json@1.0.2 26 | launch-screen@1.0.1 27 | less@1.0.12 28 | livedata@1.0.12 29 | logging@1.0.6 30 | meteor@1.1.4 31 | meteor-platform@1.2.1 32 | minifiers@1.1.3 33 | minimongo@1.0.6 34 | mobile-status-bar@1.0.2 35 | momentjs:moment@2.8.4 36 | mongo@1.0.11 37 | mrt:bootstrap-3@0.3.8 38 | mrt:jquery-ui@1.9.2 39 | mrt:moment@2.8.1 40 | observe-sequence@1.0.4 41 | ordered-dict@1.0.2 42 | pfafman:font-awesome-4@4.3.0 43 | random@1.0.2 44 | reactive-dict@1.0.5 45 | reactive-var@1.0.4 46 | reload@1.1.2 47 | retry@1.0.2 48 | routepolicy@1.0.4 49 | rzymek:fullcalendar@2.2.6_2 50 | session@1.0.5 51 | spacebars@1.0.5 52 | spacebars-compiler@1.0.4 53 | standard-app-packages@1.0.4 54 | templating@1.0.11 55 | tracker@1.0.5 56 | ui@1.0.5 57 | underscore@1.0.2 58 | url@1.0.3 59 | webapp@1.1.6 60 | webapp-hashing@1.0.2 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # this travis.yml file is for the leaderboard-nightwatch example, when run standalone 2 | language: node_js 3 | 4 | node_js: 5 | - "0.10" 6 | 7 | services: 8 | - mongodb 9 | 10 | cache: 11 | directories: 12 | - .meteor/local/build/programs/server/assets/packages 13 | - .meteor 14 | # - node_modules 15 | # - webapp/node_modules 16 | 17 | before_install: 18 | # set up the node_modules dir, so we know where it is 19 | - "mkdir -p node_modules &" 20 | # install nightwatch, so we know where it is 21 | - "npm install nightwatch" 22 | # fire up xvfb on port :99.0 23 | - "export DISPLAY=:99.0" 24 | - "sh -e /etc/init.d/xvfb start" 25 | # set the xvfb screen size to 1280x1024x16 26 | - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16" 27 | # install meteor 28 | - "curl https://install.meteor.com | /bin/sh" 29 | # give meteor a few milliseconds after installing 30 | - "sleep 5" 31 | # setup Meteor app 32 | - "cd webapp" 33 | # Optionally we can update all our dependencies here 34 | #- "meteor update" 35 | - "meteor reset" 36 | - "meteor -p 3000 &" 37 | # give Meteor some time to download packages, init data, and to start 38 | - "sleep 70" 39 | script: ./run_nightwatch.sh 40 | -------------------------------------------------------------------------------- /webapp/tests/.reports/nightwatch-acceptance/FIREFOX_31.0_MAC_walkthrough.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | scheduling 2 | ================= 3 | 4 | A basic resource scheduling applet, using the [rzymek:fullcalendar](https://atmospherejs.com/rzymek/fullcalendar) package, the D3 calendar graph, moment.js, and plenty of Meteor reactive goodness. 5 | 6 | This applet does *not* contain iron:router, so it's up to you to implement custom routing logic! 7 | 8 | Also, the calendar widget doesn't really do anything, except for being loaded into the applet. It's up to you to implement some custom logic. 9 | 10 | *note*: The demo works great on localhost; but the scheduling collection is quite big, and apparently doesn't run particularly well on the meteor.com servers. That's really a fault of the schema design of this particular calendaring widget. 11 | 12 | ![Travis Build](https://travis-ci.org/awatson1978/clinical-scheduling.svg?branch=master) 13 | 14 | ![Scheduling Screenshot](https://raw.githubusercontent.com/awatson1978/clinical-scheduling/master/webapp/public/scheduling-screenshot.png) 15 | 16 | ============================ 17 | #### Meteor Version 18 | 19 | 1.0.1 20 | 21 | 22 | ============================ 23 | #### Installation 24 | 25 | 26 | ````sh 27 | # Should be as simple as cloning the repository... 28 | git clone https://github.com/awatson1978/clinical-scheduling.git 29 | cd clinical-scheduling 30 | 31 | # And then running it... 32 | meteor 33 | ```` 34 | 35 | 36 | ============================ 37 | #### Licensing 38 | 39 | MIT License. Use as you wish, including for commercial purposes. 40 | -------------------------------------------------------------------------------- /webapp/client/app/navbars/app.navbar.html: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | 30 | 44 | -------------------------------------------------------------------------------- /webapp/client/app/calendar.less: -------------------------------------------------------------------------------- 1 | body { 2 | //font: 10px sans-serif; 3 | shape-rendering: crispEdges; 4 | 5 | } 6 | .day { 7 | fill: #fff; 8 | stroke: #ccc; 9 | } 10 | .past-reservation { 11 | fill: lightgray; 12 | } 13 | .today { 14 | fill: lightsteelblue; 15 | } 16 | .month { 17 | fill: none; 18 | stroke: #000; 19 | stroke-width: 2px; 20 | 21 | } 22 | .hour { 23 | width: 60px !important; 24 | color: #333; 25 | } 26 | .timeslot, 27 | .roomslot { 28 | border-bottom: 1px solid lightgray; 29 | padding-bottom: 3px; 30 | } 31 | .timeslot:first-of-type, 32 | .roomslot:first-of-type { 33 | border-top: 1px solid lightgray; 34 | 35 | } 36 | .label { 37 | color: #333; 38 | 39 | } 40 | .gray { 41 | fill: lightgray; 42 | } 43 | .salmon { 44 | fill: salmon; 45 | 46 | } 47 | .RdYlGn { 48 | fill: lightgray; 49 | 50 | } 51 | //.RdYlGn .q2{fill:rgb(165,0,38)} 52 | //.RdYlGn .q3{fill:rgb(215,48,39)} 53 | .RdYlGn .q3 { 54 | fill: rgb(244,109,67); 55 | } 56 | .RdYlGn .q2 { 57 | fill: rgb(253,174,97); 58 | } 59 | .RdYlGn .q1 { 60 | fill: rgb(254,224,139); 61 | } 62 | .RdYlGn .q0 { 63 | fill: rgb(217,239,139); 64 | } 65 | //.RdYlGn .q0{fill:rgb(166,217,106)} 66 | 67 | //.RdYlGn .q0{fill:rgb(102,189,99)} 68 | 69 | //.RdYlGn .q5{fill:rgb(253,174,97)} 70 | //.RdYlGn .q6{fill:rgb(254,224,139)} 71 | //.RdYlGn .q7{fill:rgb(255,255,191)} 72 | //.RdYlGn .q8{fill:rgb(217,239,139)} 73 | //.RdYlGn .q9{fill:rgb(166,217,106)} 74 | //.RdYlGn .q10{fill:rgb(102,189,99)} 75 | //.RdYlGn .q9{fill:rgb(26,152,80)} 76 | //.RdYlGn .q10{fill:rgb(0,104,55)} 77 | 78 | //.RdYlGn .q0{fill:rgb(244,109,67)} 79 | //.RdYlGn .q1{fill:rgb(253,174,97)} 80 | //.RdYlGn .q2{fill:rgb(254,224,139)} 81 | //.RdYlGn .q3{fill:rgb(255,255,191)} 82 | //.RdYlGn .q4{fill:rgb(217,239,139)} 83 | //.RdYlGn .q5{fill:rgb(166,217,106)} 84 | //.RdYlGn .q6{fill:rgb(102,189,99)} 85 | 86 | .RdYlGn .available { 87 | fill: #5cb85c; 88 | 89 | } 90 | .available { 91 | fill: #5cb85c; 92 | } 93 | .reserved { 94 | fill: #f0ad4e; 95 | } 96 | -------------------------------------------------------------------------------- /webapp/client/syntax.custom.less: -------------------------------------------------------------------------------- 1 | .page{ 2 | padding-top: 40px; 3 | padding-bottom: 40px; 4 | } 5 | 6 | //------------------------------------- 7 | 8 | 9 | .clickable{ 10 | cursor: pointer; 11 | } 12 | .unselectable{ 13 | -moz-user-select: none; 14 | -khtml-user-select: none; 15 | -webkit-user-select: none; 16 | user-select: none; 17 | } 18 | 19 | //------------------------------------- 20 | 21 | .centered{ 22 | text-align: center; 23 | } 24 | .padded{ 25 | box-sizing: border-box; 26 | padding: 10px; 27 | } 28 | .with-horizontal-padding, .horizontally-padded{ 29 | padding-left: 20px !important; 30 | padding-right: 20px !important; 31 | } 32 | .with-left-padding{ 33 | padding-left: 20px; 34 | } 35 | .with-bottom-padding{ 36 | padding-bottom: 10px !important; 37 | } 38 | .well-padded{ 39 | padding: 20px; 40 | box-sizing: border-box; 41 | } 42 | //------------------------------------- 43 | 44 | .hidden{ 45 | visibility: hidden; 46 | display: none; 47 | } 48 | 49 | //------------------------------------- 50 | .fullwidth{ 51 | width: 100%; 52 | } 53 | .fullheight{ 54 | height: 100%; 55 | } 56 | 57 | 58 | //------------------------------------- 59 | 60 | .with-rounded-corners { 61 | -moz-border-radius: .4em; 62 | -webkit-border-radius: .4em; 63 | border-radius: .4em; 64 | } 65 | 66 | 67 | .unselectable{ 68 | -moz-user-select: -moz-none; 69 | -khtml-user-select: none; 70 | -webkit-user-select: none; 71 | -ms-user-select: none; 72 | user-select: none; 73 | } 74 | 75 | 76 | //------------------------------------- 77 | .panel { 78 | background: #f2f2f2; 79 | border: solid 1px #e6e6e6; 80 | color: gray; 81 | } 82 | .dynamic:hover{ 83 | cursor:pointer; 84 | color: black; 85 | background: lightgray; 86 | } 87 | 88 | 89 | .pagination{ 90 | font-size: 24px; 91 | font-weight: bold; 92 | } 93 | 94 | 95 | //------------------------------------- 96 | 97 | .card{ 98 | border: 1px solid lightgray; 99 | } 100 | .normal-height{ 101 | height: 100px; 102 | } 103 | 104 | .large{ 105 | font-size: 24px; 106 | } 107 | 108 | .gray-line{ 109 | border-top: 1px solid lightgray; 110 | } 111 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/reservationDetails/reservationDetails.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------- 2 | // reservationDetails 3 | 4 | 5 | // finds the correct reservation slot 6 | Template.reservationDetails.helpers({ 7 | reservation: function() { 8 | return Schedule.findOne(Session.get('selected_reservation_slot')); 9 | }, 10 | reservation_color: function () { 11 | var reservations = Rooms.findOne(Session.get('selectedRoom')).reservations; 12 | if (reservations.indexOf(this._id) > -1) { 13 | return "panel-warning"; 14 | } else { 15 | return "panel-success"; 16 | } 17 | }, 18 | reservation_status: function () { 19 | var room = Rooms.findOne(Session.get('selectedRoom')); 20 | if(room){ 21 | if (room.reservations.indexOf(this._id) > -1) { 22 | return "Reserved"; 23 | } else { 24 | return "Available"; 25 | } 26 | } 27 | }, 28 | reservation_date: function () { 29 | return moment(Session.get('display_date')).format("MMM Do YYYY"); 30 | }, 31 | reserved: function () { 32 | var room = Rooms.findOne(Session.get('selectedRoom')); 33 | if(room){ 34 | if (room.reservations.indexOf(this._id) > -1) { 35 | return true; 36 | } else { 37 | return false; 38 | } 39 | } 40 | } 41 | }); 42 | 43 | 44 | 45 | 46 | //------------------------------------------------------------- 47 | // buttons 48 | 49 | 50 | 51 | // adds this reservation slot to the room's list of reservations 52 | Template.reservationDetails.events({ 53 | 'touchend #reserveRoomsButton': function() { 54 | Rooms.update(Session.get('selectedRoom'), { 55 | $addToSet: { 56 | reservations: this._id 57 | } 58 | }) 59 | Meteor.flush(); 60 | }, 61 | 'touchend #cancelReservationButton': function() { 62 | Rooms.update(Session.get('selectedRoom'), { 63 | $pull: { 64 | reservations: this._id 65 | } 66 | }) 67 | Meteor.flush(); 68 | }, 69 | 'click #reserveRoomsButton': function() { 70 | Rooms.update(Session.get('selectedRoom'), { 71 | $addToSet: { 72 | reservations: this._id 73 | } 74 | }) 75 | Session.set('last_reservation_change', new Date()); 76 | Meteor.flush(); 77 | }, 78 | 'click #cancelReservationButton': function() { 79 | console.log('click #cancelReservationButton'); 80 | Rooms.update(Session.get('selectedRoom'), { 81 | $pull: { 82 | reservations: this._id 83 | } 84 | }) 85 | Session.set('last_reservation_change', new Date()); 86 | Meteor.flush(); 87 | } 88 | }); 89 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/calendarDay/calendarDayTemplate.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------ 2 | // calendarDayTemplate 3 | // this daily calendar of 24 hours 4 | 5 | Template.calendarDayTemplate.helpers({ 6 | schedule: function(){ 7 | return Schedule.find({ 8 | date: Session.get('display_date') 9 | }); 10 | }, 11 | time: function() { 12 | if (this.hour.toString().length == 1) { 13 | return "0" + this.hour.toString() + ":00"; 14 | } else { 15 | return this.hour.toString() + ":00"; 16 | } 17 | }, 18 | reservation_status: function() { 19 | if (Session.get('selectedRoom') != "") { 20 | var reservations = Rooms.findOne(Session.get('selectedRoom')).reservations; 21 | 22 | if (reservations.indexOf(this._id) > -1) { 23 | return "reserved"; 24 | } else { 25 | return "available"; 26 | } 27 | } 28 | }, 29 | reservation_label: function() { 30 | if (Session.get('selectedRoom') != "") { 31 | var reservations = Rooms.findOne(Session.get('selectedRoom')).reservations; 32 | 33 | // right now, we're searching an array of _id strings 34 | // to find if the room has a reservation for this time slot 35 | if (reservations.indexOf(this._id) > -1) { 36 | return "appointment-warning"; 37 | } else { 38 | return "appointment-success"; 39 | } 40 | 41 | // eventually, if we want to support tracking such things as notes and who made the reservation 42 | // we're going to need to search through an array of objects, instead of an array of strings 43 | // to do that, we need a function such as .grep() or .each() 44 | 45 | // $.grep() 46 | //http://stackoverflow.com/questions/7364150/find-object-by-id-in-array-of-javascript-objects 47 | 48 | // $.each() 49 | // http://api.jquery.com/jQuery.each/ 50 | 51 | } 52 | }, 53 | inquery: function() { 54 | if (Session.get('selectedRoom')) { 55 | return true; 56 | } else { 57 | return false; 58 | } 59 | }, 60 | room: function () { 61 | if (Session.get('selectedRoom')) { 62 | return Rooms.findOne(Session.get('selectedRoom')); 63 | } else { 64 | return {}; 65 | } 66 | }, 67 | selectedRoom: function () { 68 | if (Session.get('selectedRoom')) { 69 | var room = Rooms.findOne(Session.get('selectedRoom')) 70 | if(room){ 71 | return room.name; 72 | }else{ 73 | return "---"; 74 | } 75 | } else { 76 | return "---"; 77 | } 78 | }, 79 | selected_date: function () { 80 | return moment(Session.get('display_date')).format("MMM Do YYYY"); 81 | } 82 | }); 83 | 84 | 85 | // when you click a reservation slot, set a variable 86 | Template.calendarDayTemplate.events({ 87 | 'click #nextButton': function() { 88 | Session.set('display_date', moment(Session.get('display_date')).add('d', 1).format("YYYY-MM-DD")); 89 | Session.set('selected_reservation_slot', Schedule.findOne({ 90 | date: Session.get('display_date'), 91 | hour: Session.get('selected_hour') 92 | })); 93 | }, 94 | 'click #previousButton': function() { 95 | Session.set('display_date', moment(Session.get('display_date')).subtract('d', 1).format("YYYY-MM-DD")); 96 | Session.set('selected_reservation_slot', Schedule.findOne({ 97 | date: Session.get('display_date'), 98 | hour: Session.get('selected_hour') 99 | })); 100 | }, 101 | 'click #displayedDate': function() { 102 | Session.set('display_date', moment().format("YYYY-MM-DD")); 103 | Session.set('selected_reservation_slot', Schedule.findOne({ 104 | date: Session.get('display_date'), 105 | hour: Session.get('selected_hour') 106 | })); 107 | }, 108 | 'click .timeslot': function() { 109 | Session.set('selected_hour', this.hour); 110 | Session.set('selected_reservation_slot', this._id); 111 | } 112 | }); 113 | -------------------------------------------------------------------------------- /webapp/server/initialize.schedule.js: -------------------------------------------------------------------------------- 1 | // bootstrapping the collections that run the scheduling module 2 | 3 | //schedule.date 4 | //schedule.hour 5 | //schedule.reservations 6 | //schedule.notes 7 | 8 | Meteor.startup(function () { 9 | // if there are no rooms, create sample room resources 10 | console.log('creating rooms...'); 11 | if (Rooms.find().count() === 0) { 12 | Rooms.insert({ 13 | name: 'X-Ray Exam Room 1', 14 | random: '0', 15 | reservations: [] 16 | }); 17 | Rooms.insert({ 18 | name: 'X-Ray Exam Room 2', 19 | random: '1', 20 | reservations: [] 21 | }); 22 | Rooms.insert({ 23 | name: 'X-Ray Exam Room 3', 24 | random: '2', 25 | reservations: [] 26 | }); 27 | Rooms.insert({ 28 | name: 'CT Exam Room 1', 29 | random: '3', 30 | reservations: [] 31 | }); 32 | Rooms.insert({ 33 | name: 'CT Exam Room 2', 34 | random: '4', 35 | reservations: [] 36 | }); 37 | Rooms.insert({ 38 | name: 'MRI Exam Room', 39 | random: '5', 40 | reservations: [] 41 | }); 42 | Rooms.insert({ 43 | name: 'NucMed Exam Room 1', 44 | random: '6', 45 | reservations: [] 46 | }); 47 | Rooms.insert({ 48 | name: 'Ultrasound Exam Room 2', 49 | random: '7', 50 | reservations: [] 51 | }); 52 | Rooms.insert({ 53 | name: 'Ultrasound Exam Room 3', 54 | random: '8', 55 | reservations: [] 56 | }); 57 | } 58 | 59 | // next we prepopulate reservation slots in the calendar 60 | console.log('creating schedule...'); 61 | if (Schedule.find().count() === 0) { 62 | 63 | var row = {} 64 | row.date = moment().format("YYYY-MM-DD"); 65 | 66 | var hourCount = 25; 67 | var dayOffset = 0; 68 | 69 | // we add a year's worth of timeslots 70 | for (var i = 0; i < 8760; i++) { 71 | row.hour = (i % 24) + 1; 72 | 73 | if(hourCount == 1){ 74 | hourCount = 24; 75 | dayOffset = Math.floor(i / 23); 76 | row.date = moment().add('d', dayOffset).format("YYYY-MM-DD"); 77 | }else{ 78 | hourCount--; 79 | } 80 | 81 | //console.log(JSON.stringify(row)); 82 | Schedule.insert(row); 83 | } 84 | 85 | 86 | 87 | console.log('reserving rooms...'); 88 | var roomArray = Rooms.find().fetch(); 89 | var threshold = 1; 90 | Schedule.find().forEach(function(document){ 91 | //console.log(JSON.stringify('week offset: ' + moment().diff(document.date, 'weeks'))); 92 | // a number from 0 to 52 93 | //threshold = moment().diff(document.date, 'weeks') * -1; 94 | 95 | // a number from 1 to 53 96 | //threshold = (moment().diff(document.date, 'weeks') * -1) + 1; 97 | 98 | // a number from 1 to 0.000x 99 | threshold = 1 / ((moment().diff(document.date, 'weeks') * -1) + 1); 100 | //console.log(threshold); 101 | if(Math.random() < threshold){ 102 | try{ 103 | var room = roomArray[Math.floor(Math.random() * roomArray.length)]; 104 | 105 | Rooms.update(room._id, {$addToSet: {reservations: document._id}}); 106 | console.log('room ' + room.name + ' reserved at ' + document.date + ' :: ' + document.hour + ':00'); 107 | }catch(error){ 108 | console.log(error); 109 | } 110 | } 111 | }); 112 | 113 | } 114 | 115 | 116 | 117 | console.log('everything should be all set...'); 118 | }); 119 | -------------------------------------------------------------------------------- /webapp/client/app/workflows/calendarYear/calendarYear.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | Template.calendarYear.helpers({ 4 | resized: function(){ 5 | return Session.get("resized"); 6 | }, 7 | roomSelected: function () { 8 | //renderCalendarYearChart(); 9 | return Session.get('selectedRoom'); 10 | } 11 | }); 12 | 13 | Template.calendarYear.rendered = function(){ 14 | console.log('rendering calendar...'); 15 | console.log('renderCalendarYearChart'); 16 | 17 | //d3.select(self.frameElement).style("height", "500px"); 18 | 19 | var self = $('#calendarYearGraph'); 20 | self.node = self.find("svg"); 21 | 22 | if (!self.handle) { 23 | console.log('creating reactive D3 container...'); 24 | self.handle = Tracker.autorun(function(){ 25 | var resized = Session.get('resized'); 26 | //var selectedRoomId = Session.get('selectedRoom'); 27 | 28 | 29 | console.log('setting variables...'); 30 | var width = 1024; 31 | var height = 150; 32 | var cellSize = width / 53; 33 | 34 | var day = d3.time.format("%w"); 35 | var week = d3.time.format("%U"); 36 | var percent = d3.format(".1%"); 37 | var format = d3.time.format("%Y-%m-%d"); 38 | 39 | console.log('setting color scale...'); 40 | var color = d3.scale.quantize() 41 | .domain([0, 1]) 42 | .range(d3.range(5).map(function(d) { return "q" + d; })); 43 | 44 | 45 | console.log('creating SVG canvas to draw on...'); 46 | var svg = d3.select("#calendarYearGraph").selectAll("svg") 47 | .data(d3.range(2015, 2016)) 48 | .enter().append("svg") 49 | .attr("width", width) 50 | .attr("height", height) 51 | .attr("class", "RdYlGn available") 52 | .append("g") 53 | .attr("transform", "translate(" + ((width - cellSize * 53) / 2) + "," + (height - cellSize * 7 - 1) + ")"); 54 | 55 | 56 | console.log('creating calendar rectangles for each day...'); 57 | var rect = svg.selectAll(".day") 58 | .data(function(d) { return d3.time.days(new Date(d, 0, 1), new Date(d + 1, 0, 1)); }) 59 | .enter().append("rect") 60 | .attr("class", "day") 61 | .attr("width", cellSize) 62 | .attr("height", cellSize) 63 | .attr("x", function(d) { return week(d) * cellSize; }) 64 | .attr("y", function(d) { return day(d) * cellSize; }) 65 | .datum(format); 66 | 67 | rect.append("title") 68 | .text(function(d) { return d; }); 69 | 70 | console.log('creating calendar months...'); 71 | svg.selectAll(".month") 72 | .data(function(d) { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); }) 73 | .enter().append("path") 74 | .attr("class", "month") 75 | .attr("d", monthPath); 76 | 77 | 78 | 79 | var collectionData = null; 80 | var reservationsList = null; 81 | 82 | 83 | console.log('querying room reservations...'); 84 | 85 | //console.log('selectedRoom', Session.get('selectedRoom')); 86 | var selectedRoom = Session.get('selectedRoom'); 87 | var room = Rooms.findOne(Session.get('selectedRoom')); 88 | //console.log('room', selectedRoom); 89 | 90 | if(!room){ 91 | room = {reservations: []}; 92 | } 93 | 94 | collectionData = Schedule.find({_id: {$in: room.reservations}}).fetch(); 95 | console.log('collectionData', collectionData); 96 | 97 | 98 | 99 | 100 | 101 | console.log('mapping and rolling up data...'); 102 | 103 | // this is just a weird little function to count up the number of reservations 104 | // and display appropriate colors 105 | var data = d3.nest() 106 | .key(function(d) { return d.date; }) 107 | .rollup(function(d) { return 0.05 + d3.sum(d, function(e) {return 0.1;}); }) 108 | .map(collectionData); 109 | 110 | //console.log(data); 111 | 112 | // add ids to graph so jquery can attack to it 113 | rect.attr("id", function(d) {return "day-" + d; }); 114 | rect.attr("class", function(d) {return "day"}); 115 | 116 | // addClass and removeClass don't work on SVG elements 117 | // so we need to remove the q0, q1, q2, q3, and q4 classes 118 | // by querying the .day class, and resetting the class attribute 119 | $('.day').attr('class', 'day'); 120 | 121 | // we've been having major scoping 122 | for(var key in data){ 123 | if(data.hasOwnProperty(key)){ 124 | //console.log(key + ' ' + data[key]); 125 | if(data[key] < 0.21){ 126 | d3.select('#day-' + key).attr("class", "day q0"); 127 | }else if((data[key] > 0.21) && (data[key] < 0.41)){ 128 | d3.select('#day-' + key).attr("class", "day q1"); 129 | }else if((data[key] > 0.41) && (data[key] < 0.61)){ 130 | d3.select('#day-' + key).attr("class", "day q2"); 131 | }else if((data[key] > 0.61) && (data[key] < 0.81)){ 132 | d3.select('#day-' + key).attr("class", "day q3"); 133 | }else{ 134 | d3.select('#day-' + key).attr("class", "day q4"); 135 | } 136 | } 137 | } 138 | 139 | 140 | //console.log('creating month path...'); 141 | function monthPath(t0) { 142 | var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0), 143 | d0 = +day(t0), w0 = +week(t0), 144 | d1 = +day(t1), w1 = +week(t1); 145 | return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize 146 | + "H" + w0 * cellSize + "V" + 7 * cellSize 147 | + "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize 148 | + "H" + (w1 + 1) * cellSize + "V" + 0 149 | + "H" + (w0 + 1) * cellSize + "Z"; 150 | } 151 | 152 | }); 153 | }; 154 | }; 155 | Template.calendarYear.destroyed = function(){ 156 | this.handle && this.handle.stop(); 157 | }; 158 | -------------------------------------------------------------------------------- /webapp/run_nightwatch.sh: -------------------------------------------------------------------------------- 1 | #!/bin//bash 2 | # run this file from your application's root directory 3 | 4 | # this will trap any errors or commands with non-zero exit status 5 | # by calling function catch_errors() 6 | trap catch_errors ERR; 7 | function catch_errors() { 8 | EXITCODE=$? 9 | # do whatever on errors 10 | echo "" 11 | echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" 12 | echo "nightwatch aborted with a status code of $EXITCODE" 13 | echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" 14 | exit $EXITCODE; 15 | } 16 | 17 | echo "Setting variables..." 18 | # can not use set -e, sadly - nightwatch bug? 19 | #set -e 20 | 21 | # defaults 22 | CONFIG=".meteor/local/build/programs/server/assets/packages/clinical_nightwatch/nightwatch_from_app_root.json" 23 | ENVIRONMENT="default" 24 | _TESTS="" 25 | _GROUP="" 26 | _SKIP="" 27 | _FILTER="" 28 | _VERBOSE="" 29 | _HELP="" 30 | _ARGS=false 31 | 32 | function usage() 33 | { 34 | echo "Nightwatch Test Runner" 35 | echo "" 36 | echo "./run_nightwatch.sh" 37 | echo "\t-H this script's help" 38 | echo "\t-h --help Nightwatch's help" 39 | echo "\t-e | --environment=$ENVIRONMENT (CSV)" 40 | echo "\t-c | --config=$CONFIG" 41 | echo "\t-t | --test which test file to run (webapp path)" 42 | echo "\t-g | --group which group of tests to run" 43 | echo "\t-s | --skipgroup of group to skip (CSV)" 44 | echo "\t-f | --filter test file names (glob expression)i eg: '*table*'" 45 | echo "\t-v | --verbose see more output from Selenium" 46 | echo "" 47 | } 48 | 49 | # Execute getopt on the arguments passed to this program, identified by the special character $@ 50 | PARSED_OPTIONS=$(getopt -n "$0" -o "hHe:c:t:g:s:f:v" --long "help,HELP,env:,config:,test:,group:,skipgroup:,filter:,verbose" -- "$@") 51 | 52 | # A little magic, necessary when using getopt. 53 | eval set -- "$PARSED_OPTIONS" 54 | #echo "PARSED_OPTIONS: $PARSED_OPTIONS" 55 | 56 | echo "Checking user defined options..." 57 | echo "$8" 58 | echo "$9" 59 | 60 | echo "Parsing options..." 61 | # Now goes through all the options with a case and using shift to analyse 1 argument at a time. 62 | #$1 identifies the first argument, and when we use shift we discard the first argument, so $2 becomes $1 and goes again through the case. 63 | 64 | if [ $9 ] 65 | then 66 | _ARGS=true 67 | fi 68 | 69 | while $_ARGS; 70 | do 71 | case "$8" in 72 | -H | --HELP) 73 | usage 74 | exit;; 75 | -h | --help) 76 | _HELP=" --help" 77 | shift;; 78 | -v | --verbose) 79 | _VERBOSE=" --verbose" 80 | shift;; 81 | -c | --config) 82 | if [ -n "$2" ]; 83 | then 84 | CONFIG=$2 85 | fi 86 | shift 9;; 87 | -e | -E | --env | --environment) 88 | if [ -n "$2" ]; then 89 | ENVIRONMENT=$2 90 | fi 91 | shift 9;; 92 | -t) 93 | #echo 'found a -t' 94 | if [ -n "$9" ]; then 95 | _TESTS=" -t $9" 96 | #echo $_TESTS 97 | fi 98 | #shift 9;; 99 | break;; 100 | -g | --group) 101 | if [ -n "$2" ]; then 102 | _GROUP=" -g $2" 103 | fi 104 | shift 9;; 105 | -s | --skipgroup) 106 | if [ -n "$2" ]; then 107 | _SKIP=" -s $s" 108 | fi 109 | shift 9;; 110 | -f | --filter) 111 | if [ -n "$2" ]; then 112 | _FILTER=" -f $2" 113 | fi 114 | shift 9;; 115 | --) 116 | shift 117 | break;; 118 | esac 119 | done 120 | 121 | 122 | # TODO cd into the app dir as PWD 123 | 124 | echo "Changing file permissions..." 125 | # Setup: changing file permissions" 126 | mkdir -p .meteor/local/build/programs/server/assets/packages/clinical_nightwatch 127 | chmod +x .meteor/local/build/programs/server/assets/packages/clinical_nightwatch/launch_nightwatch*.sh 128 | chmod +x .meteor/local/build/programs/server/assets/packages/clinical_nightwatch/selenium/selenium-server-standalone-2.42.0.jar 129 | mkdir -p tests/logs 130 | chmod 0777 tests/logs 131 | 132 | 133 | echo "Looking for Nightwatch executables..." 134 | # Setup: look for Nightwatch executable 135 | NWBIN=".meteor/local/build/node_modules/nightwatch/bin/nightwatch" 136 | if [ ! -f $NWBIN ]; then 137 | NWBIN="node_modules/nightwatch/bin/nightwatch" 138 | fi 139 | if [ ! -f $NWBIN ]; then 140 | NWBIN="../node_modules/nightwatch/bin/nightwatch" 141 | fi 142 | if [ ! -f $NWBIN ]; then 143 | NWBIN="../../node_modules/nightwatch/bin/nightwatch" 144 | fi 145 | if [ ! -f $NWBIN ]; then 146 | NWBIN="../../node_modules/nightwatch/bin/nightwatch" 147 | fi 148 | if [ ! -f $NWBIN ]; then 149 | # Setup: Nightwatch executable not found, install it 150 | echo " installing nightwatch in .meteor/local/build" 151 | # or, depending on Node, might roll up to Dart repo parent 152 | cd .meteor/local/build 153 | npm install nightwatch@0.5.36 154 | cd ../../.. 155 | NWBIN=".meteor/local/build/node_modules/nightwatch/bin/nightwatch" 156 | fi 157 | if [ ! -f $NWBIN ]; then 158 | echo "ERROR: nightwatch binary - File not found!" 159 | PWD=`pwd` 160 | echo " pwd: $PWD" 161 | echo " NWBIN: $NWBIN" 162 | echo "ls .meteor/local/build/node_modules/" 163 | ls .meteor/local/build/node_modules/ 164 | exit 1 165 | fi 166 | 167 | 168 | # Build and run nightwatch command 169 | # always include the ENVIRONMENT and CONFIG params 170 | CMD="$NWBIN -e $ENVIRONMENT -c $CONFIG$_TESTS$_GROUP$_SKIP$_FILTER$_VERBOSE$_HELP" 171 | echo "Stitching together command to run: $CMD" 172 | 173 | 174 | echo "Running Nightwatch..." 175 | if [ -n "$_VERBOSE" ]; then 176 | echo "" 177 | echo " $NWBIN" 178 | echo " -e $ENVIRONMENT" 179 | echo " -c $CONFIG" 180 | echo " $_TESTS (test)" 181 | echo " $_GROUP (group)" 182 | echo " $_SKIP (skip)" 183 | echo " $_FILTER (filter)" 184 | echo " $_VERBOSE (verbose)" 185 | echo " $_HELP (help)" 186 | fi 187 | echo "" 188 | echo "" 189 | 190 | # run nightwatch command 191 | eval $CMD 192 | 193 | # if we made it here... we're good 194 | # errors/exits should have been trapped above 195 | echo "" 196 | echo "Nightwatch completed with a status code of $?." 197 | echo "" 198 | echo " ,.," 199 | echo " MMMM_ ,..," 200 | echo " ^_ ^__^MMMMM ,...,," 201 | echo " ,..., __.^ --^ ,., _-^MMMMMMM" 202 | echo " MMMMMM^___ ^_._ MMM^_.^^ _ ^^^^^^^" 203 | echo " ^^^^^ ^^ , \_. ^_. .^" 204 | echo " ,., _^__ \__./ .^" 205 | echo " MMMMM_^ ^_ ./" 206 | echo " ```` ( )" 207 | echo " ._______________.-^____^---._." 208 | echo " \ /" 209 | echo " \________________________/" 210 | echo " (_) (_)" 211 | echo "" 212 | echo " You Win!" 213 | 214 | 215 | # return exit code 0 216 | exit 0 217 | --------------------------------------------------------------------------------