diff --git a/.gitignore b/.gitignore index 3673992f..0dda205f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ yarn-debug.log* # Ignore database.yml because it differs between OS /config/database.yml +/config/database.yml # Ignore vscode settings .vscode/ \ No newline at end of file diff --git a/app/controllers/assignments_controller.rb b/app/controllers/assignments_controller.rb index de4e5df4..bb013cba 100644 --- a/app/controllers/assignments_controller.rb +++ b/app/controllers/assignments_controller.rb @@ -2,6 +2,7 @@ class AssignmentsController < ApplicationController before_action :set_action_item, only:[:show, :edit] def index @assignments = authorize Assignment.all + @user = current_user skip_policy_scope end diff --git a/app/controllers/studio_assessments_controller.rb b/app/controllers/studio_assessments_controller.rb index b31a376b..d81f89a7 100644 --- a/app/controllers/studio_assessments_controller.rb +++ b/app/controllers/studio_assessments_controller.rb @@ -5,6 +5,7 @@ class StudioAssessmentsController < ApplicationController # GET /studio_assessments.json def index @studio_assessments = StudioAssessment.all + @user = current_user skip_policy_scope end diff --git a/app/javascript/components/ActionItemCards/index.js b/app/javascript/components/ActionItemCards/index.js new file mode 100644 index 00000000..24fa8941 --- /dev/null +++ b/app/javascript/components/ActionItemCards/index.js @@ -0,0 +1,116 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + faLeaf, + faHome, + faPen, + faFile, + faSmile, + faCode, +} from '@fortawesome/free-solid-svg-icons'; +import { withStyles } from '@material-ui/core/styles'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import styles from './styles'; + +class ActionItemCard extends React.Component { + constructor(props) { + super(props); + /* + TODO: check props types + tempory props rn to test components + props: + actionItem - Javascripy object + title - Assignment title: String + description - Short description: String + date - Due date of assignment: JavaScript date? + category - type of assignment: string?? + status - status of item: string + */ + + } + + // render icon based on category + renderIcon() { + const AssignmentCategory = this.props.actionItem.category; + let icon = null; + switch (AssignmentCategory) { + case 'house': + icon = ( + + ); + break; + case 'leaf': + icon = ( + + ); + break; + case 'pen': + icon = ( + + ); + break; + case 'file': + icon = ( + + ); + break; + case 'smile': + icon = ( + + ); + break; + case 'code': + icon = ( + + ); + break; + } + return icon; + } + + render() { + return ( +
+
+ {this.renderIcon()} +
+
{this.props.actionItem.description}
+
+ DATE ASSIGNED: {this.props.actionItem.date.toLocaleDateString()} +
+
+
VIEW ASSIGNMENT
+
+ ); + } +} + +ActionItemCard.propTypes = { + actionItem: PropTypes.object, +}; + +export default withStyles(styles)(ActionItemCard); diff --git a/app/javascript/components/ActionItemCards/styles.js b/app/javascript/components/ActionItemCards/styles.js new file mode 100644 index 00000000..bf44e27d --- /dev/null +++ b/app/javascript/components/ActionItemCards/styles.js @@ -0,0 +1,46 @@ +export const styles = () => ({ + card: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'start', + border: '0.5px solid #5870EB', + }, + rectangle: { + width: '5px', + height: '70.93px', + background: '#5870EB', + borderRadius: '20px', + }, + assign: { + fontFamily: 'Inter', + fontStyle: 'normal', + fontWeight: 'bold', + fontSize: '12px', + lineHeight: '16px', + color: '#5870EB', + }, + '& :active': { + color: 'rgb(58, 74, 156)', + }, + text: { + color: '#8E8E8E', + fontFamily: 'Inter', + fontStyle: 'normal', + fontSize: '8px', + }, + date: { + color: '#8E8E8E', + fontFamily: 'Inter', + fontStyle: 'normal', + fontWeight: '500', + fontSize: '12px', + lineHeight: '16px', + }, + info: { + display: 'flex', + flexDirection: 'column', + flexGrow: '2', + }, +}); + +export default styles; diff --git a/app/javascript/components/ActionItemDashboard/index.js b/app/javascript/components/ActionItemDashboard/index.js new file mode 100644 index 00000000..d0ac2bf8 --- /dev/null +++ b/app/javascript/components/ActionItemDashboard/index.js @@ -0,0 +1,42 @@ +import React from 'react'; +import ActionItemCard from 'components/ActionItemCards'; +import Navbar from 'components/Navbar'; +import PropTypes from 'prop-types'; + +const action = { + title: 'Finish Sprint', + description: 'Complete the sprint by tonight please. Thank you', + date: new Date(), + category: 'code', +}; + +class ActionItemDashboard extends React.Component { + constructor(props) { + super(props); + this.state = { + assignments: this.props.assignments, + }; + } + + render() { + return ( +
+ +
+

Assignment Dashboard

+
+
+ +
+
+
+
+ ); + } +} + +ActionItemDashboard.propTypes = { + assignments: PropTypes.array, +}; + +export default ActionItemDashboard; diff --git a/app/javascript/components/Navbar/index.js b/app/javascript/components/Navbar/index.js index 096c1354..c299d864 100644 --- a/app/javascript/components/Navbar/index.js +++ b/app/javascript/components/Navbar/index.js @@ -3,6 +3,10 @@ import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; import { IconButton, Button, Grid, Drawer } from '@material-ui/core'; import HomeIcon from '@material-ui/icons/Home'; +import GroupIcon from '@material-ui/icons/Group'; +import BarChartIcon from '@material-ui/icons/BarChart'; +import AccountCircleIcon from '@material-ui/icons/AccountCircle'; +import ExitToAppIcon from '@material-ui/icons/ExitToApp'; import UnloopLogo from 'images/unloop_logo.png'; import * as Sentry from '@sentry/browser'; import styles from './styles'; @@ -12,22 +16,30 @@ function Navbar({ classes, isAdmin }) { const navigateToAdminBoard = () => { window.location.href = '/admin'; }; - const navigateToHomepage = () => { window.location.href = '/'; Sentry.configureScope(scope => scope.setUser(null)); }; + const navigateToAssignments = () => { + window.location.href = '/assignments'; + }; + const navigateToStudio = () => { + window.location.href = '/studio_assessments'; + }; + const renderAdminButton = () => ( - + + ); const logout = () => { @@ -63,22 +75,49 @@ function Navbar({ classes, isAdmin }) { className={classes.navBarItem} onClick={logout} > - Sign Out + +
Sign Out
{isAdmin ? {renderAdminButton()} : null} - - - + +
Dashboard
+ +
+ + + + + - + ({ navBar: { height: '100vh', @@ -19,6 +20,8 @@ const styles = theme => ({ navBarItem: { color: theme.palette.common.white, textAlign: 'center', + + }, unloopLogo: { width: '100%', @@ -26,6 +29,16 @@ const styles = theme => ({ objectFit: 'contain', overflowX: 'hidden', }, + navText: { + fontFamily: 'Roboto', + fontWeight: 500, + fontSize: '10px', + lineHeight: '16px', + letterSpacing: '1.5px', + textTransform: 'uppercase', + color: white, + }, + }); export default styles; diff --git a/app/javascript/components/ParticipantCard/index.js b/app/javascript/components/ParticipantCard/index.js index 9449b624..0ff33761 100644 --- a/app/javascript/components/ParticipantCard/index.js +++ b/app/javascript/components/ParticipantCard/index.js @@ -41,6 +41,7 @@ function ParticipantCard({ classes, participant }) { >
); + const caseNotes = numCaseNotes === 1 ? `${numCaseNotes} case note` @@ -63,6 +64,7 @@ function ParticipantCard({ classes, participant }) {
{participant.paperworksCompleted} / {numPaperworks} completed{' '} +
({ - participants: prevState.trie.get(searchVal), - })); + const participants = this.state.trie.get(searchVal); + this.setState({ + participants, + }); } render() { diff --git a/app/javascript/components/StudioAssessmentCard/index.js b/app/javascript/components/StudioAssessmentCard/index.js new file mode 100644 index 00000000..de01e40a --- /dev/null +++ b/app/javascript/components/StudioAssessmentCard/index.js @@ -0,0 +1,62 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + + +class StudioAssessmentCard extends React.Component { + constructor(props) { + super(props); + this.showParticipant = this.showParticipant.bind(this); + } + + showParticipant() { + const pId = this.props.assessment.participant_id; + window.location.assign(`participants/${String(pId)}`); + } + + render() { + const assessment = this.props.assessment; + const bigPic = assessment.bigpicture_score; + const prog = assessment.progfundamentals_score; + const vc = assessment.versioncontrol_score; + const react = assessment.react_score; + const node = assessment.node_score; + const db = assessment.db_score; + + return ( + + + {assessment.participant_id} + + + {bigPic} + + + {prog} + + + {vc} + + + {react} + + + {node} + + + {db} + + + ); + } +} + +StudioAssessmentCard.propTypes = { + assessment: PropTypes.object, +}; + +export default StudioAssessmentCard ; diff --git a/app/javascript/components/StudioAssessmentDashboard/index.js b/app/javascript/components/StudioAssessmentDashboard/index.js new file mode 100644 index 00000000..5bd92ee7 --- /dev/null +++ b/app/javascript/components/StudioAssessmentDashboard/index.js @@ -0,0 +1,60 @@ +import React from 'react'; +import Navbar from 'components/Navbar'; +import PropTypes from 'prop-types'; +import StudioAssessmentCard from 'components/StudioAssessmentCard'; +import styles from './styles'; +import { withStyles, ThemeProvider } from '@material-ui/core/styles'; +import theme from 'utils/theme'; + +class StudioAssessmentDashboard extends React.Component { + constructor(props) { + super(props); + this.state = { + assessments: this.props.assessments, + }; + } + + render() { + const { classes } = this.props; + let assessmentsList = this.state.assessments.map((p, i) => ( + + )); + return ( + +
+ +
+

Studio Assessments

+
+
+ + + + + + + + + + + + + + {assessmentsList} + +
ParticipantBig PictureProg FundamentalsVersion Control React Node Db
+
+
+
+
+
+ ); + } +} + +StudioAssessmentDashboard.propTypes = { + classes: PropTypes.object.isRequired, + assessments: PropTypes.array, +}; + +export default withStyles(styles)(StudioAssessmentDashboard); diff --git a/app/javascript/components/StudioAssessmentDashboard/styles.js b/app/javascript/components/StudioAssessmentDashboard/styles.js new file mode 100644 index 00000000..91a279f1 --- /dev/null +++ b/app/javascript/components/StudioAssessmentDashboard/styles.js @@ -0,0 +1,104 @@ +const backgroundBlue = '#d2dce1'; +const white = '#fff'; + +const styles = () => ({ + dashboard: { + height: '100%', + width: '100%', + fontStyle: 'normal', + + '& thead': { + '& th': { + paddingBottom: 16, + }, + borderBottom: '2px solid #eb6658', + }, + '& h1': { + marginLeft: 50, + fontWeight: 550, + fontSize: 36, + }, + }, + '@global': { + button: { + cursor: 'pointer', + border: 'none', + '&:focus': { + outline: 'none', + }, + }, + body: { + margin: 0, + maxWidth: 100, + cursor: 'default', + fontFamily: 'Roboto, sans-serif', + fontStyle: 'normal', + }, + html: { + margin: 0, + maxWidth: 100, + cursor: 'default', + fontFamily: 'Roboto, sans-serif', + fontStyle: 'normal', + }, + table: { + width: '100%', + borderCollapse: 'collapse', + fontSize: 20, + fontFamily: "'Roboto', sans-serif", + fontStyle: 'normal', + + '& th, & td': { + padding: '25px 3%', + textAlign: 'left', + borderBottom: '1px solid #ddd', + }, + + '& tbody': { + '& tr:hover': { + backgroundColor: '#f5f5f5', + }, + }, + + '& td': { + '& > div': { + fontSize: 18, + fontWeight: 300, + fontStyle: 'normal', + }, + }, + }, + }, + tableContainer: { + height: '100%', + minHeight: '100vh', + backgroundColor: backgroundBlue, + padding: '20px 42px', + '& > div': { + backgroundColor: white, + boxShadow: '0px 4px 10px rgba(0, 0, 0, 0.25)', + borderRadius: 20, + padding: 30, + }, + }, + content: { + position: 'absolute', + left: '8%', + width: 'calc(92%)', + display: 'inline-block', + }, + searchBar: { + height: 40, + marginLeft: 30, + borderRadius: 2, + width: 260, + border: '1px solid #bdbdbd', + paddingLeft: 10, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, +}); + +export default styles; diff --git a/app/policies/assignment_policy.rb b/app/policies/assignment_policy.rb index e2c49e93..b3e5e1bf 100644 --- a/app/policies/assignment_policy.rb +++ b/app/policies/assignment_policy.rb @@ -39,4 +39,4 @@ def resolve def staff? user.present? && user.staff? end - end \ No newline at end of file + end diff --git a/app/views/assignments/index.html.erb b/app/views/assignments/index.html.erb index 48dbe0b7..4fa30642 100644 --- a/app/views/assignments/index.html.erb +++ b/app/views/assignments/index.html.erb @@ -1,3 +1,5 @@ + +<%= react_component("ActionItemDashboard", { is_admin: @user.admin?}) %>

Assignments

diff --git a/app/views/studio_assessments/index.html.erb b/app/views/studio_assessments/index.html.erb index 08e7f52a..b172f5a4 100644 --- a/app/views/studio_assessments/index.html.erb +++ b/app/views/studio_assessments/index.html.erb @@ -1,3 +1,4 @@ +<% if false %>

<%= notice %>

Studio Assessments

@@ -33,8 +34,8 @@ <% @studio_assessments.each do |studio_assessment| %> - - + + @@ -53,8 +54,8 @@ - + @@ -66,3 +67,6 @@
<%= link_to 'New Studio Assessment', new_studio_assessment_path %> +<% end %> + +<%= react_component("StudioAssessmentDashboard", {assessments: @studio_assessments, is_admin: @user.admin? }) %> diff --git "a/config/database.yml\342\200\251" "b/config/database.yml\342\200\251" new file mode 100644 index 00000000..81980273 --- /dev/null +++ "b/config/database.yml\342\200\251" @@ -0,0 +1,85 @@ +# PostgreSQL. Versions 9.3 and up are supported. +# +# Install the pg driver: +# gem install pg +# On macOS with Homebrew: +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config +# On macOS with MacPorts: +# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config +# On Windows: +# gem install pg +# Choose the win32 build. +# Install PostgreSQL and put its /bin directory on your path. +# +# Configure Using Gemfile +# gem 'pg' +# +default: &default + adapter: postgresql + encoding: unicode + # For details on connection pooling, see Rails configuration guide + # https://guides.rubyonrails.org/configuring.html#database-pooling + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + +development: + <<: *default + database: unloop_development + + # The specified database role being used to connect to postgres. + # To create additional roles in postgres see `$ createuser --help`. + # When left blank, postgres will use the default role. This is + # the same name as the operating system user that initialized the database. + #username: unloop + + # The password associated with the postgres role (username). + #password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + + # The TCP port the server listens on. Defaults to 5432. + # If your server runs on a different port number, change accordingly. + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # Defaults to warning. + #min_messages: notice + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: unloop_test + +# As with config/credentials.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read https://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: unloop_production + username: unloop + password: <%= ENV['UNLOOP_DATABASE_PASSWORD'] %> diff --git a/config/routes.rb b/config/routes.rb index cf7fe829..9800f21a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,6 +14,10 @@ resources :assignments, :paperworks, :case_notes, :professional_questionnaires, :personal_questionnaires, only: [:index, :show, :new, :edit] + get '/assignments', to: 'assignments#index' + + get '/studio_assessments', to: 'studio_assessments#index' + resources :staffs, only: [] do collection do get 'dashboard', to: 'staffs#dashboard' diff --git a/db/seeds.rb b/db/seeds.rb index 17e022ba..730dc203 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,7 +7,7 @@ NUM_PROF_QUESTIONNAIRE = 25 NUM_TEMPLATE_ACTION_ITEMS = 10 NUM_ACTION_ITEMS = 25 - +NUM_STUDIO_ASSESSMENTS = 25 STAFF_START_ID = Staff.count + 1 STAFF_END_ID = STAFF_START_ID + NUM_STAFF - 1 PARTICIPANT_START_ID = Participant.count + 1 @@ -144,6 +144,34 @@ def create_google_accounts end end +def create_studio_assesments + 1.upto(NUM_STUDIO_ASSESSMENTS) do |i| + StudioAssessment.create!( + bigpicture_score: Faker::Number.between(from: 0, to: 3), + bigpicture_comment: Faker::Cannabis.buzzword, + progfundamentals_score: Faker::Number.between(from: 0, to: 3), + progfundamentals_comment: Faker::Cannabis.buzzword, + versioncontrol_score: Faker::Number.between(from: 0, to: 3), + versioncontrol_comment: Faker::Cannabis.buzzword, + react_score: Faker::Number.between(from: 0, to: 3), + react_comment: Faker::Cannabis.buzzword, + node_score: Faker::Number.between(from: 0, to: 3), + node_comment: Faker::Cannabis.buzzword, + db_score: Faker::Number.between(from: 0, to: 3), + db_comment: Faker::Cannabis.buzzword, + problemsolving_score: Faker::Number.between(from: 0, to: 3), + problemsolving_comment:Faker::Cannabis.buzzword, + problemsolvingalt_score: Faker::Number.between(from: 0, to: 3), + problemsolvingalt_comment:Faker::Cannabis.buzzword, + passed_capstone: Faker::Boolean.boolean, + capstone_comment:Faker::Cannabis.buzzword, + assessment_type: Faker::Hacker.say_something_smart, + staff_id: Faker::Number.between(from: STAFF_START_ID, to: STAFF_END_ID), + participant_id: Faker::Number.between(from: PARTICIPANT_START_ID, to: PARTICIPANT_END_ID) + ) + end + puts "Created #{NUM_STUDIO_ASSESSMENTS} Studio Assessments" +end create_staff create_participants @@ -154,3 +182,4 @@ def create_google_accounts create_questionnaires create_template_action_items create_assignments +create_studio_assesments diff --git a/spec/controllers/assignments_controller_spec.rb b/spec/controllers/assignments_controller_spec.rb new file mode 100644 index 00000000..3652be7b --- /dev/null +++ b/spec/controllers/assignments_controller_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +RSpec.describe AssignmentsController, type: :controller do + + describe "GET #index" do + it "returns http success" do + get :index + expect(response).to have_http_status(:success) + end + end + +end diff --git a/spec/helpers/assignments_helper_spec.rb b/spec/helpers/assignments_helper_spec.rb new file mode 100644 index 00000000..2bb91536 --- /dev/null +++ b/spec/helpers/assignments_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the AssignmentsHelper. For example: +# +# describe AssignmentsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe AssignmentsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/policies/assignment_policy_spec.rb b/spec/policies/assignment_policy_spec.rb new file mode 100644 index 00000000..f87ead4a --- /dev/null +++ b/spec/policies/assignment_policy_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +RSpec.describe AssignmentPolicy, type: :policy do + let(:user) { User.new } + + subject { described_class } + + permissions ".scope" do + pending "add some examples to (or delete) #{__FILE__}" + end + + permissions :show? do + pending "add some examples to (or delete) #{__FILE__}" + end + + permissions :create? do + pending "add some examples to (or delete) #{__FILE__}" + end + + permissions :update? do + pending "add some examples to (or delete) #{__FILE__}" + end + + permissions :destroy? do + pending "add some examples to (or delete) #{__FILE__}" + end +end diff --git a/spec/views/assignments/index.html.erb_spec.rb b/spec/views/assignments/index.html.erb_spec.rb new file mode 100644 index 00000000..1d9f1c6a --- /dev/null +++ b/spec/views/assignments/index.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "assignments/index.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end
<%= studio_assessment.participant.id %><%= studio_assessment.staff.id %><%= studio_assessment.participant%><%= studio_assessment.staff%> <%= studio_assessment.bigpicture_score %> <%= studio_assessment.bigpicture_comment %> <%= studio_assessment.progfundamentals_score %><%= studio_assessment.problemsolvingalt_comment %> <%= studio_assessment.passed_capstone %> <%= studio_assessment.capstone_comment %><%= studio_assessment.assessment_type %><%= studio_assessment.created_at%> <%= link_to 'Show', studio_assessment %> <%= link_to 'Edit', edit_studio_assessment_path(studio_assessment) %> <%= link_to 'Destroy', studio_assessment, method: :delete, data: { confirm: 'Are you sure?' } %>