diff --git a/.gitmodules b/.gitmodules index acfb63e..66f828b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "assets/libs/enketo-core"] path = assets/libs/enketo-core url = git@github.com:MartijnR/enketo-core.git +[submodule "src/vendor/jquery-toastmessage"] + path = src/vendor/jquery-toastmessage + url = git@github.com:akquinet/jquery-toastmessage-plugin.git diff --git a/Gruntfile.js b/Gruntfile.js index e813743..b58e08e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -94,6 +94,7 @@ module.exports = function(grunt) { 'assets/scripts/website.min.js': [ 'src/vendor/chosen/chosen.jquery.min.js', + 'src/vendor/jquery-toastmessage/src/main/javascript/jquery.toastmessage.js', 'src/scripts/*.js' ], } @@ -106,6 +107,7 @@ module.exports = function(grunt) { files : { 'assets/styles/main.css' : [ 'src/vendor/chosen/chosen.css', + 'src/vendor/jquery-toastmessage/src/main/resources/css/jquery.toastmessage.css', 'src/temp/path_override.css', 'src/temp/main.css' ] @@ -115,6 +117,7 @@ module.exports = function(grunt) { files : { 'assets/styles/main.css' : [ 'src/vendor/chosen/chosen.min.css', + 'src/vendor/jquery-toastmessage/src/main/resources/css/jquery.toastmessage.css', 'src/temp/path_override.css', 'src/temp/main.css' ] diff --git a/application/config/config.php b/application/config/config.php index a76f74e..5d216c2 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -14,8 +14,8 @@ | path to your installation. | */ -//$config['base_url'] = 'http://dev/airwolf2.0/aw-datacollection/'; $config['base_url'] = 'http://192.168.99.10/work/aw-datacollection/'; +//$config['base_url'] = '/work/aw-datacollection/'; /* |-------------------------------------------------------------------------- @@ -384,5 +384,11 @@ $config['aw_respondents_per_page'] = 50; +/** + * Email settings and admin data + */ +$config['aw_admin_name'] = 'Aw-datacollection Admin'; +$config['aw_admin_email'] = 'aw-datacollection@airwolf.edispilf.org'; + /* End of file config.php */ /* Location: ./application/config/config.php */ \ No newline at end of file diff --git a/application/config/routes.php b/application/config/routes.php index 1d7d23c..93f1aab 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -47,7 +47,7 @@ $route['survey/add'] = 'survey/survey_add'; $route['survey/(:num)'] = 'survey/survey_by_id/$1'; $route['survey/(:num)/edit'] = 'survey/survey_edit_by_id/$1'; -$route['survey/delete'] = 'survey/survey_delete_by_id'; +$route['survey/(:num)/delete'] = 'survey/survey_delete_by_id/$1'; $route['survey/(:num)/files/(xls|xml)'] = 'survey/survey_file_download/$1/$2'; $route['survey/(:num)/(testrun|data_collection)'] = 'survey/survey_enketo/$1/$2'; // respondents @@ -74,7 +74,7 @@ // Users $route['login'] = 'user/user_login'; $route['logout'] = 'user/user_logout'; -$route['user'] = 'user/user_profile'; +//$route['user'] = 'user/user_profile'; As of right now profiles are disabled. $route['user/(:num)/edit'] = 'user/user_edit_by_id/$1'; $route['user/recover'] = 'user/user_recover_password'; $route['user/reset_password/(:any)'] = 'user/user_reset_password/$1'; diff --git a/application/controllers/index.php b/application/controllers/index.php index 7af6a92..9e225bf 100644 --- a/application/controllers/index.php +++ b/application/controllers/index.php @@ -5,12 +5,22 @@ class Index extends CI_Controller { public function __construct() { parent::__construct(); } + + public function test() { + $this->load->view('base/html_start'); + $this->load->view('frontend'); + $this->load->view('base/html_end'); + } public function index() { - $this->load->view('base/html_start'); - $this->load->view('navigation'); + if (!is_logged()) { + redirect('login'); + } $this->load->model('survey_model'); + $this->load->view('base/html_start'); + $this->load->view('components/navigation', array('active_menu' => 'dashboard')); + // Use the same permissions for the list but use different statuses. $surveys = array(); if (has_permission('view survey list any')) { @@ -23,21 +33,22 @@ public function index() { } // TEMP + $data = ''; if ($surveys) { - $this->output->append_output('Your surveys:
'); + $data = 'Your surveys:
'; foreach ($surveys as $survey) { - $this->output->append_output('-' . $survey->title . '
'); + $data .= '-' . $survey->title . '
'; } ob_start(); krumo($surveys); - $this->output->append_output(ob_get_clean()); + $data .= ob_get_clean(); } // /TEMP - - + + $this->load->view('dashboard', array('data' => $data)); $this->load->view('base/html_end'); } } diff --git a/application/controllers/survey.php b/application/controllers/survey.php index ccf0db9..1cc859f 100644 --- a/application/controllers/survey.php +++ b/application/controllers/survey.php @@ -37,7 +37,7 @@ public function index() { * Route: * /surveys */ - public function surveys_list(){ + public function surveys_list(){ if (!has_permission('view survey list any') && !has_permission('view survey list assigned')) { show_403(); } @@ -55,7 +55,7 @@ public function surveys_list(){ } $this->load->view('base/html_start'); - $this->load->view('navigation'); + $this->load->view('components/navigation', array('active_menu' => 'surveys')); $this->load->view('surveys/survey_list', array('surveys' => $surveys)); $this->load->view('base/html_end'); @@ -84,11 +84,8 @@ public function survey_by_id($sid){ } } - $messages = Status_msg::get(); $data = array( - 'survey' => $survey, - //'messages' => $messages, - 'messages' => $this->load->view('messages', array('messages' => $messages), TRUE) + 'survey' => $survey ); // Agents. Each array element contains the user and @@ -111,7 +108,7 @@ public function survey_by_id($sid){ $data['agents'] = $agents; $this->load->view('base/html_start'); - $this->load->view('navigation'); + $this->load->view('components/navigation', array('active_menu' => 'surveys')); $this->load->view('surveys/survey_page', $data); $this->load->view('base/html_end'); @@ -182,7 +179,7 @@ protected function _survey_form_handle($action = 'add', $survey = null) { // If no data submitted show the form. if ($this->form_validation->run() == FALSE) { $this->load->view('base/html_start'); - $this->load->view('navigation'); + $this->load->view('components/navigation', array('active_menu' => 'surveys')); $this->load->view('surveys/survey_form', array('survey' => $survey)); $this->load->view('base/html_end'); } @@ -276,24 +273,28 @@ protected function _survey_form_handle($action = 'add', $survey = null) { /** * Delete handler for surveys. - * Route (POST data) - * /survey/delete + * Route + * /survey/:sid/delete */ - public function survey_delete_by_id(){ + public function survey_delete_by_id($sid){ + verify_csrf_get(); + if (!has_permission('delete any survey')) { show_403(); } - - $this->form_validation->set_rules('survey_sid', 'Survey ID', 'required|callback__cb_survey_exists'); - $sid = $this->input->post('survey_sid'); - - if ($this->form_validation->run() == TRUE) { - $this->survey_model->delete($sid); + + $survey = $this->survey_model->get($sid); + if (!$survey) { + show_404(); + } + + if ($this->survey_model->delete($sid)) { + Status_msg::success('Survey successfully deleted.'); } else { - // Survey Id has been tempered with. - show_error("An error occurred while deleting the survey."); + Status_msg::error('An error occurred while deleting the survey.'); } + redirect('/surveys'); } @@ -333,26 +334,29 @@ public function survey_enketo($sid, $type) { else if (!$survey->has_xml()) { show_403(); } - + + // If can collect|testrun any let through, otherwise must be assigned. switch ($type) { case 'data_collection': - if (!has_permission('enketo collect data any') && !has_permission('enketo collect data assigned')) { - show_403(); - - }else if (!has_permission('enketo collect data any') && has_permission('enketo collect data assigned')) { - // Is assigned? - if (!$survey->is_assigned_agent(current_user()->uid)) { - show_403(); - } - } + $perm_any = 'enketo collect data any'; + $perm_assigned = 'enketo collect data assigned'; break; case 'testrun': - if (!has_permission('enketo testrun any') && !has_permission('enketo testrun assigned')) { - show_403(); - } + $perm_any = 'enketo testrun any'; + $perm_assigned = 'enketo testrun assigned'; break; } + + if (!has_permission($perm_any) && !has_permission($perm_assigned)) { + show_403(); + + }else if (!has_permission($perm_any) && has_permission($perm_assigned)) { + // Is assigned? + if (!$survey->is_assigned_agent(current_user()->uid)) { + show_403(); + } + } // Needed urls. $settings = array( @@ -366,7 +370,7 @@ public function survey_enketo($sid, $type) { $this->js_settings->add($settings); $this->load->view('base/html_start', array('using_enketo' => TRUE, 'enketo_action' => $type)); - $this->load->view('navigation'); + $this->load->view('components/navigation', array('active_menu' => 'surveys')); $this->load->view('surveys/survey_enketo', array('survey' => $survey, 'enketo_action' => $type)); $this->load->view('base/html_end'); @@ -415,7 +419,7 @@ public function survey_enketo_single($sid, $ctid) { $this->js_settings->add($settings); $this->load->view('base/html_start', array('using_enketo' => TRUE, 'enketo_action' => 'data_collection_single')); - $this->load->view('navigation'); + $this->load->view('components/navigation', array('active_menu' => 'surveys')); $this->load->view('surveys/survey_enketo', array('survey' => $survey, 'call_task' => $call_task, 'enketo_action' => 'data_collection_single')); $this->load->view('base/html_end'); } @@ -454,7 +458,7 @@ public function survey_call_activity($sid) { $unresolved = $this->call_task_model->get_unresolved($sid, current_user()->uid); $this->load->view('base/html_start'); - $this->load->view('navigation'); + $this->load->view('components/navigation', array('active_menu' => 'surveys')); $this->load->view('surveys/survey_call_activity', array( 'survey' => $survey, 'call_tasks_resolved' => $resolved, @@ -463,6 +467,243 @@ public function survey_call_activity($sid) { $this->load->view('base/html_end'); } + /** + * Summary page to list the respondents associated to a given survey. + * @param $sid + * + * Route - /survey/:sid/respondents/:page + */ + public function survey_respondents($sid, $page = 1){ + $page = $page < 1 ? 1 : $page; + $survey = $this->survey_model->get($sid); + if (!$survey) { + show_404(); + } + else if (!has_permission('manage respondents any survey')) { + show_403(); + } + + // Respondents to show per page. + $respondents_pp = $this->config->item('aw_respondents_per_page'); + $this->load->library('pagination'); + + // Prepare pagination. + $this->pagination->initialize(array( + 'base_url' => $survey->get_url_respondents(), + 'use_page_numbers' => TRUE, + 'uri_segment' => 4, + 'total_rows' => $this->call_task_model->get_total_count($sid), + 'per_page' => $respondents_pp, + //'cur_tag_open' => '', + //'cur_tag_close' => '', + )); + + $respondents = $this->call_task_model->get_all_paginated($sid, $page, $respondents_pp); + + $data = array( + 'survey' => $survey, + 'respondents' => $respondents, + ); + + $this->load->view('base/html_start'); + $this->load->view('components/navigation', array('active_menu' => 'surveys')); + $this->load->view('surveys/survey_respondents', $data); + $this->load->view('base/html_end'); + } + + /** + * Summary page to add respondents to a given survey. + * @param $sid + * + * Route - /survey/:sid/respondents + */ + public function survey_respondents_add($sid){ + $survey = $this->survey_model->get($sid); + if (!$survey) { + show_404(); + } + else if (!has_permission('manage respondents any survey')) { + show_403(); + } + + // Config data for the file upload. + $file_upload_config = array( + 'upload_path' => '/tmp/', + 'allowed_types' => 'csv', + 'file_name' => 'respondents_' . md5(microtime(true)) + ); + + // Load needed libraries + $this->load->library('upload', $file_upload_config); + + $this->form_validation->set_rules('survey_respondents_file', 'Respondents File', 'callback__cb_survey_respondents_add_file_handle'); + $this->form_validation->set_rules('survey_respondents_text', 'Respondents Text', 'xss_clean'); + + // If no data submitted show the form. + if ($this->form_validation->run() == FALSE) { + + $this->load->view('base/html_start'); + $this->load->view('components/navigation', array('active_menu' => 'surveys')); + $this->load->view('surveys/survey_respondents_add', array('survey' => $survey)); + $this->load->view('base/html_end'); + } + else { + // Initialize the respondents numbers list. + $rows = explode("\n", $this->input->post('survey_respondents_text')); + $textarea_lines = sizeof($rows); + $respondents_numbers = array(); + + // Read file, if any. + $file = $this->input->post('survey_respondents_file'); + + if (isset($file['full_path'])) { + // Load CSVReader library. + $this->load->helper('csvreader'); + $csv = new CSVReader(); + $csv->separator = ','; + // We are merging the rows from csv to potential rows in the text area + // this allows to verify everything in one pass. + $rows = array_merge($rows, $csv->parse_file($file['full_path'])); + } + + $db_call_tasks = $this->call_task_model->get_all($sid); + + foreach ($rows as $line => $row) { + // Silently skip empty rows. + if (empty($row)) { + continue; + } + + // Prepare data. + // If it's a row from the text area. + if (!is_array($row)) { + $context = 'textarea'; + $real_line = $line + 1; + $row = trim($row); + } + // This is from the csv file. + else { + $context = 'CSV file'; + $real_line = ($line + 1) - $textarea_lines; + // Make sure it's not a random CSV. + if (isset($row[SURVEY_RESPONDENT_CSV_HEADER])) { + $row = trim($row[SURVEY_RESPONDENT_CSV_HEADER]); + } + // Warn user. + else { + Status_msg::warning('Some data has been skipped. Make sure your column header is "' . SURVEY_RESPONDENT_CSV_HEADER .'".'); + } + } + + // Common checks. + // @todo check also if already present in the DB + // hint: check $row against $object->number + if (is_numeric($row)) { + + // Check db doubles. + $is_double = FALSE; + foreach ($db_call_tasks as $call_task) { + if ($call_task->number == $row) { + $is_double = TRUE; + break; + } + } + + if ($is_double) { + // Db Double. + continue; + } + // END Check db doubles. + + if (!isset($respondents_numbers[$row])) { + $respondents_numbers[$row] = 0; + } + $respondents_numbers[$row]++; + } + else { + Status_msg::warning("Line #$real_line of the $context has been skipped as it does not appear to be a number."); + } + } + + // Store in session or issue error if. + if (sizeof($respondents_numbers)) { + $_SESSION['respondents_numbers'] = $respondents_numbers; + } + else { + Status_msg::error('No usable numbers have been found in submitted data.'); + redirect('/survey/' . $survey->sid . '/respondents'); + } + + // Perform the redirect. + redirect('/survey/' . $survey->sid . '/respondents/add/confirm'); + } + } + + /** + * Summary page to add respondents to a given survey. + * @param $sid + * + * Route - /survey/:sid/respondents + */ + public function survey_respondents_add_confirm($sid){ + $survey = $this->survey_model->get($sid); + if (!$survey) { + show_404(); + } + else if (!has_permission('manage respondents any survey')) { + show_403(); + } + + $data = array( + 'survey' => $survey, + 'respondents_numbers' => array(), + ); + + // pass on the data to the view + if (isset($_SESSION['respondents_numbers'])) { + $data['respondents_numbers'] = array_keys($_SESSION['respondents_numbers']); + } + + $this->form_validation->set_rules('survey_respondents_submit', '', 'required'); + + // If no data submitted show the form. + if ($this->form_validation->run() == FALSE) { + + $this->load->view('base/html_start'); + $this->load->view('components/navigation', array('active_menu' => 'surveys')); + $this->load->view('surveys/survey_respondents_confirm', $data); + $this->load->view('base/html_end'); + } + else { + + // create a call_task for each respondent number + foreach ($_SESSION['respondents_numbers'] as $number => $qtd) { + // Prepare survey data to construct a new survey_entity + $call_task_data = array(); + // @todo find generic solution for the code below (int) + $call_task_data['survey_sid'] = (int) $sid; + $call_task_data['number'] = (string) $number; + + // Construct survey. + $new_call_task = Call_task_entity::build($call_task_data); + + // Save survey. + // Survey files can only be handled after the survey is saved. + // TODO: Handle error during save. + $this->call_task_model->save($new_call_task); + } + + // User feedback. + $numbers = sizeof($_SESSION['respondents_numbers']); + Status_msg::success("Added $numbers entries to the call tasks of this survey."); + + unset($_SESSION['respondents_numbers']); + + // perform the redirect + redirect('/survey/' . $survey->sid . '/respondents'); + } + } + /** * Enketo API * Converts the survey xml file to html for enketo to use @@ -883,250 +1124,6 @@ public function _cb_survey_respondents_add_file_handle() { } - /** - * Summary page to list the respondents associated to a given survey. - * @param $sid - * - * Route - /survey/:sid/respondents/:page - */ - public function survey_respondents($sid, $page = 1){ - $page = $page < 1 ? 1 : $page; - $survey = $this->survey_model->get($sid); - if (!$survey) { - show_404(); - } - else if (!has_permission('manage respondents any survey')) { - show_403(); - } - - // Respondents to show per page. - $respondents_pp = $this->config->item('aw_respondents_per_page'); - $this->load->library('pagination'); - - // Prepare pagination. - $this->pagination->initialize(array( - 'base_url' => $survey->get_url_respondents(), - 'use_page_numbers' => TRUE, - 'uri_segment' => 4, - 'total_rows' => $this->call_task_model->get_total_count($sid), - 'per_page' => $respondents_pp, - //'cur_tag_open' => '', - //'cur_tag_close' => '', - )); - - $respondents = $this->call_task_model->get_all_paginated($sid, $page, $respondents_pp); - - $messages = Status_msg::get(); - $data = array( - 'survey' => $survey, - 'respondents' => $respondents, - //'messages' => $messages, - 'messages' => $this->load->view('messages', array('messages' => $messages), TRUE) - ); - - $this->load->view('base/html_start'); - $this->load->view('navigation'); - $this->load->view('surveys/survey_respondents', $data); - $this->load->view('base/html_end'); - } - - /** - * Summary page to add respondents to a given survey. - * @param $sid - * - * Route - /survey/:sid/respondents - */ - public function survey_respondents_add($sid){ - $survey = $this->survey_model->get($sid); - if (!$survey) { - show_404(); - } - else if (!has_permission('manage respondents any survey')) { - show_403(); - } - - // Config data for the file upload. - $file_upload_config = array( - 'upload_path' => '/tmp/', - 'allowed_types' => 'csv', - 'file_name' => 'respondents_' . md5(microtime(true)) - ); - - // Load needed libraries - $this->load->library('upload', $file_upload_config); - - $this->form_validation->set_rules('survey_respondents_file', 'Respondents File', 'callback__cb_survey_respondents_add_file_handle'); - $this->form_validation->set_rules('survey_respondents_text', 'Respondents Text', 'xss_clean'); - - // If no data submitted show the form. - if ($this->form_validation->run() == FALSE) { - - $messages = Status_msg::get(); - - $this->load->view('base/html_start'); - $this->load->view('navigation'); - $this->load->view('surveys/survey_respondents_add', array('survey' => $survey, 'messages' => $messages)); - $this->load->view('base/html_end'); - } - else { - // Initialize the respondents numbers list. - $rows = explode("\n", $this->input->post('survey_respondents_text')); - $textarea_lines = sizeof($rows); - $respondents_numbers = array(); - - // Read file, if any. - $file = $this->input->post('survey_respondents_file'); - - if (isset($file['full_path'])) { - // Load CSVReader library. - $this->load->helper('csvreader'); - $csv = new CSVReader(); - $csv->separator = ','; - // We are merging the rows from csv to potential rows in the text area - // this allows to verify everything in one pass. - $rows = array_merge($rows, $csv->parse_file($file['full_path'])); - } - - $db_call_tasks = $this->call_task_model->get_all($sid); - - foreach ($rows as $line => $row) { - // Silently skip empty rows. - if (empty($row)) { - continue; - } - - // Prepare data. - // If it's a row from the text area. - if (!is_array($row)) { - $context = 'textarea'; - $real_line = $line + 1; - $row = trim($row); - } - // This is from the csv file. - else { - $context = 'CSV file'; - $real_line = ($line + 1) - $textarea_lines; - // Make sure it's not a random CSV. - if (isset($row[SURVEY_RESPONDENT_CSV_HEADER])) { - $row = trim($row[SURVEY_RESPONDENT_CSV_HEADER]); - } - // Warn user. - else { - Status_msg::warning('Some data has been skipped. Make sure your column header is "' . SURVEY_RESPONDENT_CSV_HEADER .'".'); - } - } - - // Common checks. - // @todo check also if already present in the DB - // hint: check $row against $object->number - if (is_numeric($row)) { - - // Check db doubles. - $is_double = FALSE; - foreach ($db_call_tasks as $call_task) { - if ($call_task->number == $row) { - $is_double = TRUE; - break; - } - } - - if ($is_double) { - // Db Double. - continue; - } - // END Check db doubles. - - if (!isset($respondents_numbers[$row])) { - $respondents_numbers[$row] = 0; - } - $respondents_numbers[$row]++; - } - else { - Status_msg::warning("Line #$real_line of the $context has been skipped as it does not appear to be a number."); - } - } - - // Store in session or issue error if. - if (sizeof($respondents_numbers)) { - $_SESSION['respondents_numbers'] = $respondents_numbers; - } - else { - Status_msg::error('No usable numbers have been found in submitted data.'); - redirect('/survey/' . $survey->sid . '/respondents'); - } - - // Perform the redirect. - redirect('/survey/' . $survey->sid . '/respondents/add/confirm'); - } - } - - /** - * Summary page to add respondents to a given survey. - * @param $sid - * - * Route - /survey/:sid/respondents - */ - public function survey_respondents_add_confirm($sid){ - $survey = $this->survey_model->get($sid); - if (!$survey) { - show_404(); - } - else if (!has_permission('manage respondents any survey')) { - show_403(); - } - - $messages = Status_msg::get(); - $data = array( - 'survey' => $survey, - 'messages' => $this->load->view('messages', array('messages' => $messages), TRUE), - 'respondents_numbers' => array(), - ); - - // pass on the data to the view - if (isset($_SESSION['respondents_numbers'])) { - $data['respondents_numbers'] = array_keys($_SESSION['respondents_numbers']); - } - - $this->form_validation->set_rules('survey_respondents_submit', '', 'required'); - - // If no data submitted show the form. - if ($this->form_validation->run() == FALSE) { - - $this->load->view('base/html_start'); - $this->load->view('navigation'); - $this->load->view('surveys/survey_respondents_confirm', $data); - $this->load->view('base/html_end'); - } - else { - - // create a call_task for each respondent number - foreach ($_SESSION['respondents_numbers'] as $number => $qtd) { - // Prepare survey data to construct a new survey_entity - $call_task_data = array(); - // @todo find generic solution for the code below (int) - $call_task_data['survey_sid'] = (int) $sid; - $call_task_data['number'] = (string) $number; - - // Construct survey. - $new_call_task = Call_task_entity::build($call_task_data); - - // Save survey. - // Survey files can only be handled after the survey is saved. - // TODO: Handle error during save. - $this->call_task_model->save($new_call_task); - } - - // User feedback. - $numbers = sizeof($_SESSION['respondents_numbers']); - Status_msg::success("Added $numbers entries to the call tasks of this survey."); - - unset($_SESSION['respondents_numbers']); - - // perform the redirect - redirect('/survey/' . $survey->sid . '/respondents'); - } - } } - /* End of file survey.php */ -/* Location: ./application/controllers/survey.php */ +/* Location: ./application/controllers/survey.php */ \ No newline at end of file diff --git a/application/controllers/user.php b/application/controllers/user.php index 4c1bdf6..3999a7b 100644 --- a/application/controllers/user.php +++ b/application/controllers/user.php @@ -32,7 +32,6 @@ public function user_login() { if ($this->form_validation->run() == FALSE) { $this->load->view('base/html_start'); - $this->load->view('navigation'); $this->load->view('login'); $this->load->view('base/html_end'); } @@ -58,13 +57,16 @@ public function user_logout() { * /user */ public function user_profile($uid = null) { + // Profiles are disabled. + redirect(); + // Viewing other user's profile is not a requirement. // Viewing the current user profile requires the user // to be logged in. It is not something to control through // a permission. if (is_logged()) { $this->load->view('base/html_start'); - $this->load->view('navigation'); + $this->load->view('components/navigation', array('active_menu' => 'users')); $this->load->view('users/user_profile', array('user' => current_user())); $this->load->view('base/html_end'); } @@ -74,7 +76,7 @@ public function user_profile($uid = null) { } /** - * Logout. + * Edit user by id. * Route: * /user/(:num)/edit */ @@ -120,7 +122,7 @@ protected function _edit_own_account() { if ($this->form_validation->run() == FALSE) { $this->load->view('base/html_start'); - $this->load->view('navigation'); + $this->load->view('components/navigation', array('active_menu' => 'users')); $this->load->view('users/user_form', array('user' => $user, 'action' => 'edit_own')); $this->load->view('base/html_end'); } @@ -151,7 +153,7 @@ protected function _edit_other_account($user) { if ($this->form_validation->run() == FALSE) { $this->load->view('base/html_start'); - $this->load->view('navigation'); + $this->load->view('components/navigation', array('active_menu' => 'users')); $this->load->view('users/user_form', array('user' => $user, 'action' => 'edit_other')); $this->load->view('base/html_end'); } @@ -198,7 +200,7 @@ protected function _add_account() { if ($this->form_validation->run() == FALSE) { $this->load->view('base/html_start'); - $this->load->view('navigation'); + $this->load->view('components/navigation', array('active_menu' => 'users')); $this->load->view('users/user_form', array('user' => NULL, 'action' => 'add')); $this->load->view('base/html_end'); } @@ -259,7 +261,6 @@ public function user_recover_password() { if ($this->form_validation->run() == FALSE) { $this->load->view('base/html_start'); - $this->load->view('navigation'); $this->load->view('users/user_recover_password'); $this->load->view('base/html_end'); } @@ -271,15 +272,15 @@ public function user_recover_password() { if ($hash) { $this->load->library('email'); - // TODO: Email data. Use settings as much as possible. - $this->email->from('aw-datacollection@airwolf.edispilf.org', 'Aw-datacollection Admin'); - $this->email->to('daniel.silva@flipside.org'); + $this->email->from($this->config->item('aw_admin_email'), $this->config->item('aw_admin_name')); + $this->email->to($email); - $this->email->subject('Password Recover'); + $this->email->subject('Airwolf - Recover Password'); $this->email->message('Use the following link. ' . base_url('user/reset_password/' . $hash)); $this->email->send(); - // TODO: Message user. Check your email. + + Status_msg::success('Please check your email for next steps.', TRUE); redirect('login'); } else { @@ -305,7 +306,6 @@ public function user_reset_password($hash) { if ($this->form_validation->run() == FALSE) { $this->load->view('base/html_start'); - $this->load->view('navigation'); $this->load->view('users/user_reset_password'); $this->load->view('base/html_end'); } @@ -317,11 +317,12 @@ public function user_reset_password($hash) { if ($this->user_model->save($user)) { $this->recover_password_model->invalidate($hash); - // TODO: Message user. Login with your new password. + + Status_msg::success('Password successfully changed. You can now login.', TRUE); redirect('login'); } else { - show_error("Error saving your new password. Try again later."); + Status_msg::error("Error saving your new password. Try again later."); } } @@ -354,7 +355,7 @@ public function users_list() { $users = $this->user_model->get_all(); $this->load->view('base/html_start'); - $this->load->view('navigation'); + $this->load->view('components/navigation', array('active_menu' => 'users')); $this->load->view('users/user_list', array('users' => $users)); $this->load->view('base/html_end'); } diff --git a/application/entities/survey_entity.php b/application/entities/survey_entity.php index 9537646..81ee595 100644 --- a/application/entities/survey_entity.php +++ b/application/entities/survey_entity.php @@ -36,6 +36,20 @@ class Survey_entity extends Entity { Survey_entity::STATUS_CLOSED => 'Closed', Survey_entity::STATUS_CANCELED => 'Canceled', ); + + /** + * Html classes for survey status. + * + * @var array + * @access public + * @static + */ + static $statuses_html_classes = array( + Survey_entity::STATUS_DRAFT => 'status-draft', + Survey_entity::STATUS_OPEN => 'status-open', + Survey_entity::STATUS_CLOSED => 'status-closed', + Survey_entity::STATUS_CANCELED => 'status-canceled', + ); /******************************** ******************************** @@ -264,6 +278,30 @@ public function get_url_edit() { return base_url('survey/' . $this->sid . '/edit') ; } + /** + * Returns the url to delete a survey. + * @access public + * @return string + */ + public function get_url_delete() { + if ($this->sid == NULL) { + throw new Exception("Trying to get link for a non-existent survey."); + } + return base_url('survey/' . $this->sid . '/delete') ; + } + + /** + * Returns the url to download survey file. + * @access public + * @return string + */ + public function get_url_file($type) { + if ($this->sid == NULL) { + throw new Exception("Trying to get link for a non-existent survey."); + } + return base_url('survey/' . $this->sid . '/files/' . $type) ; + } + /** * Returns the url to test run survey. * @access public @@ -432,7 +470,6 @@ public function has_xls() { /** * Returns the full path to the survey's xls file. * - * * @return mixed * If the survey has no file false is returned */ @@ -443,13 +480,38 @@ public function get_xls_full_path() { /** * Returns the full path to the survey's xls file. * - * * @return mixed * If the survey has no file false is returned */ public function get_xml_full_path() { return $this->has_xml() ? $this->settings['file_loc'] . $this->files['xml'] : false; } + + /** + * Returns the survey status in human readable format. + * + * @return string + * The survey status in human readable format + */ + public function get_status_label() { + if (!$this->status){ + return NULL; + } + return Survey_entity::$statuses[$this->status]; + } + + /** + * Returns the survey status for use as html class. + * + * @return string + * The survey status for use as html class. + */ + public function get_status_html_class($prefix = '') { + if (!$this->status){ + return NULL; + } + return $prefix . Survey_entity::$statuses_html_classes[$this->status]; + } /** * Checks if an agent is assigned to the survey. diff --git a/application/helpers/general_utils_helper.php b/application/helpers/general_utils_helper.php index fc4ebce..ef5e54e 100644 --- a/application/helpers/general_utils_helper.php +++ b/application/helpers/general_utils_helper.php @@ -103,6 +103,44 @@ function show_403() { } } +if ( ! function_exists('verify_csrf_get')) { + /** + * CSRF verification for GET requests. + * This function verifies that the get request made to the page + * carries the CSRF token. If it not present it till throw the + * standard error. + * Assumes the param name is the token name. + */ + function verify_csrf_get() { + $CI = get_instance(); + $csrf_token_name = $CI->security->get_csrf_token_name(); + $csrf_cookie_name = config_item('cookie_prefix') . config_item('csrf_cookie_name'); + $link_token = $CI->input->get($csrf_token_name); + + // Is token set? + if (!$link_token || !isset($_COOKIE[$csrf_cookie_name])){ + $CI->security->csrf_show_error(); + } + + // Do the tokens match? + if ($link_token != $_COOKIE[$csrf_cookie_name]) { + $CI->security->csrf_show_error(); + } + + $CI->security->csrf_verify(); + } +} + +if ( ! function_exists('anchor_csrf')) { + function anchor_csrf($uri = '', $title = '', $attributes = '') { + $CI = get_instance(); + $csrf_token_name = $CI->security->get_csrf_token_name(); + $csrf_hash = $CI->security->get_csrf_hash(); + + $uri .= "?$csrf_token_name=$csrf_hash"; + return anchor($uri, $title, $attributes); + } +} // ------------------------------------------------------------------------ diff --git a/application/helpers/status_msg_helper.php b/application/helpers/status_msg_helper.php index 9d156e5..648735d 100644 --- a/application/helpers/status_msg_helper.php +++ b/application/helpers/status_msg_helper.php @@ -17,28 +17,41 @@ class Status_msg { private static $userdata_key = 'status_msg'; /** - * Stores success messages. + * Stores messages in the same order has they were set. */ - private static $success = array(); - + private static $messages = array(); + /** - * Stores warning messages. + * Sets a notice message. + * + * @static */ - private static $warning = array(); + public static function set($msg, $level, $sticky = FALSE) { + self::$messages[] = array( + 'msg' => $msg, + 'level' => $level, + 'sticky' => $sticky, + 'time' => time(), + ); + self::store(); + } /** - * Stores error messages. + * Sets a notice message. + * + * @static */ - private static $error = array(); + public static function notice($msg, $sticky = TRUE) { + self::set($msg, 'notice', $sticky); + } /** * Sets a success message. * * @static */ - public static function success($msg) { - self::$success[] = $msg; - self::store(); + public static function success($msg, $sticky = FALSE) { + self::set($msg, 'success', $sticky); } /** @@ -47,9 +60,8 @@ public static function success($msg) { * * @static */ - public static function warning($msg) { - self::$warning[] = $msg; - self::store(); + public static function warning($msg, $sticky = TRUE) { + self::set($msg, 'warning', $sticky); } /** @@ -58,9 +70,8 @@ public static function warning($msg) { * * @static */ - public static function error($msg) { - self::$error[] = $msg; - self::store(); + public static function error($msg, $sticky = TRUE) { + self::set($msg, 'error', $sticky); } /** @@ -69,11 +80,7 @@ public static function error($msg) { */ public static function store() { $CI =& get_instance(); - $CI->session->set_userdata(self::$userdata_key, array( - 'success' => self::$success, - 'warning' => self::$warning, - 'error' => self::$error - )); + $CI->session->set_userdata(self::$userdata_key, self::$messages); } /** diff --git a/application/models/survey_model.php b/application/models/survey_model.php index cece610..4e92e6a 100644 --- a/application/models/survey_model.php +++ b/application/models/survey_model.php @@ -93,6 +93,8 @@ public function delete($sid) { $result = $this->mongo_db ->where('sid', (int) $sid) ->delete(self::COLLECTION); + + return $result !== FALSE ? TRUE : FALSE; } /** diff --git a/application/views/base/footer_scripts.php b/application/views/base/footer_scripts.php index fbb84cf..a723c54 100644 --- a/application/views/base/footer_scripts.php +++ b/application/views/base/footer_scripts.php @@ -1,9 +1,12 @@ + +load->view('components/toast_messages'); ?> + + - - \ No newline at end of file + + \ No newline at end of file diff --git a/application/views/base/head_styles.php b/application/views/base/head_styles.php index 7966c16..7f5e365 100644 --- a/application/views/base/head_styles.php +++ b/application/views/base/head_styles.php @@ -3,4 +3,5 @@ + \ No newline at end of file diff --git a/application/views/base/html_end.php b/application/views/base/html_end.php index 9741d8f..eddabed 100644 --- a/application/views/base/html_end.php +++ b/application/views/base/html_end.php @@ -1,3 +1,4 @@ + load->view('base/footer_scripts') ?> \ No newline at end of file diff --git a/application/views/base/html_start.php b/application/views/base/html_start.php index f2f03c5..fdf4a2d 100644 --- a/application/views/base/html_start.php +++ b/application/views/base/html_start.php @@ -9,4 +9,5 @@ load->view('base/head_styles') ?> - \ No newline at end of file + +
\ No newline at end of file diff --git a/application/views/components/navigation.php b/application/views/components/navigation.php new file mode 100644 index 0000000..d537e8a --- /dev/null +++ b/application/views/components/navigation.php @@ -0,0 +1,72 @@ + \ No newline at end of file diff --git a/application/views/components/toast_messages.php b/application/views/components/toast_messages.php new file mode 100644 index 0000000..f278af3 --- /dev/null +++ b/application/views/components/toast_messages.php @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/application/views/dashboard.php b/application/views/dashboard.php new file mode 100644 index 0000000..4e6723b --- /dev/null +++ b/application/views/dashboard.php @@ -0,0 +1,5 @@ +
+
+ +
+
\ No newline at end of file diff --git a/application/views/frontend.php b/application/views/frontend.php new file mode 100644 index 0000000..98464d5 --- /dev/null +++ b/application/views/frontend.php @@ -0,0 +1,145 @@ +
+ + +
+
+ + + +
+ +
+
+
+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StatusTitleActions
DraftUNHCO Client Satisfaction Survey
OpenGIZ Water Campaign February 2013
ClosedLorem Ipsum Dolor Sit Amet
CanceledThis will never come to be
+
+ + +
+
+
+
+ + + +
\ No newline at end of file diff --git a/application/views/login.php b/application/views/login.php index fde2a45..9bb15c5 100644 --- a/application/views/login.php +++ b/application/views/login.php @@ -1,17 +1,35 @@ -
+ -', '
'); ?> -', ''); ?> - - 'login-form')); ?> - - - - - - - - - - - +
+
+ '); ?> + + 'login-form')); ?> + + + + + + + + + + +
+ + + + + +
\ No newline at end of file diff --git a/application/views/messages.php b/application/views/messages.php deleted file mode 100644 index fae400f..0000000 --- a/application/views/messages.php +++ /dev/null @@ -1,16 +0,0 @@ - -
- $messages_level): ?> - - -
- -
- - -
- \ No newline at end of file diff --git a/application/views/navigation.php b/application/views/navigation.php deleted file mode 100644 index 2e44ecf..0000000 --- a/application/views/navigation.php +++ /dev/null @@ -1,70 +0,0 @@ - - - - \ No newline at end of file diff --git a/application/views/surveys/survey_call_activity.php b/application/views/surveys/survey_call_activity.php index 510944c..ae16257 100644 --- a/application/views/surveys/survey_call_activity.php +++ b/application/views/surveys/survey_call_activity.php @@ -1,6 +1,91 @@ -
- -

Completed

+is_assigned_agent(current_user()->uid)){ + $show_actions_enketo_data_collection = TRUE; +} + +$show_actions_enketo_testrun = FALSE; +if (has_permission('enketo testrun any')) { + $show_actions_enketo_testrun = TRUE; +} +else if (has_permission('enketo testrun assigned') && $survey->is_assigned_agent(current_user()->uid)){ + $show_actions_enketo_testrun = TRUE; +} +?> + +
+
+ + + + + + +

Completed

@@ -13,9 +98,9 @@ - - - + + + @@ -35,12 +120,20 @@ - - - + + +
number ?>Last status hereno actionsnumber ?>Last status hereno actions
number ?>Collect datanumber ?>Collect data
-
\ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/application/views/surveys/survey_form.php b/application/views/surveys/survey_form.php index e2171f9..74a589c 100644 --- a/application/views/surveys/survey_form.php +++ b/application/views/surveys/survey_form.php @@ -1,21 +1,57 @@ -
- - - - - - - - - - - +
+
+ + + + + + + + + + + + + + + + + + + - -
\ No newline at end of file + + \ No newline at end of file diff --git a/application/views/surveys/survey_list.php b/application/views/surveys/survey_list.php index 4b7f88c..bbac57e 100644 --- a/application/views/surveys/survey_list.php +++ b/application/views/surveys/survey_list.php @@ -1,68 +1,70 @@ -
- - - - - - - - - - - - - - - - + + + +
TitleStatusActions
title ?>status; ?> -
    - -
  • Edit
  • - - - -
  • - sid); - print form_submit(array( - 'name' => 'survey_delete', - 'value' => 'Delete', - 'class' => 'button tiny' - )); - print form_close(); - ?> -
  • - - - - -
  • Respondents
  • - - - has_xml()) : ?> -
  • Test Run
  • +
    +
    + + + +
    +
    +
    +
    + + + +
    + +
    + + + + + + + + + + + + + + + + - - - - -
    StatusTitleDate
    get_status_label(); ?>title ?> + + get_url_edit(), 'Edit', array('class' => 'bttn bttn-small bttn-primary')); ?> + - is_assigned_agent(current_user()->uid)){ - $show_actions_enketo_data_collection = TRUE; - } - - if ($show_actions_enketo_data_collection) :?> -
  • Collect Data
  • -
  • Call activity
  • - - - - -
    -
    + + get_url_delete(), 'Delete', array('class' => 'bttn bttn-small bttn-danger')); ?> + +
+
+ + + + + + \ No newline at end of file diff --git a/application/views/surveys/survey_page.php b/application/views/surveys/survey_page.php index d75ccb5..e732e97 100644 --- a/application/views/surveys/survey_page.php +++ b/application/views/surveys/survey_page.php @@ -1,28 +1,98 @@ -
+is_assigned_agent(current_user()->uid)){ + $show_actions_enketo_data_collection = TRUE; +} - +$show_actions_enketo_testrun = FALSE; +if (has_permission('enketo testrun any')) { + $show_actions_enketo_testrun = TRUE; +} +else if (has_permission('enketo testrun assigned') && $survey->is_assigned_agent(current_user()->uid)){ + $show_actions_enketo_testrun = TRUE; +} +?> - Status: status ?> -

title ?>

+
+
+ + + + +
+

Introduction

introduction) ?>
- - - files['xls'] !== NULL) : ?> -
xls: xls
- - - files['xml'] !== NULL) : ?> -
xml: xml
- - - - - - Manage respondents - + +

Manage Agents

@@ -36,4 +106,13 @@ -
\ No newline at end of file +
+ + + + + + + + + \ No newline at end of file diff --git a/application/views/surveys/survey_respondents.php b/application/views/surveys/survey_respondents.php index 1bdb2ea..d218b47 100644 --- a/application/views/surveys/survey_respondents.php +++ b/application/views/surveys/survey_respondents.php @@ -1,28 +1,52 @@ -
- - - - Status: status ?> -

title ?>

-

Respondents list

- - - - Add respondents - - - - - - - - +
+
+ + + + + +
NumberActions
- - + + - -
number; ?>NumberActions
- - pagination->create_links(); ?> -
+ + + number; ?> + + + + + + pagination->create_links(); ?> + + + + + + + \ No newline at end of file diff --git a/application/views/surveys/survey_respondents_add.php b/application/views/surveys/survey_respondents_add.php index 4f8bc69..3b5d423 100644 --- a/application/views/surveys/survey_respondents_add.php +++ b/application/views/surveys/survey_respondents_add.php @@ -1,30 +1,50 @@ - -
- - - - - Status: status ?> -

title ?>

-

Add respondents

- -
- introduction) ?> -
- - - - - - - - - - - View respondents - - - -
- - +
+
+ + + + + + + + +

Add respondents

+ + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/application/views/surveys/survey_respondents_confirm.php b/application/views/surveys/survey_respondents_confirm.php index adc9e22..456c16b 100644 --- a/application/views/surveys/survey_respondents_confirm.php +++ b/application/views/surveys/survey_respondents_confirm.php @@ -1,25 +1,54 @@ - -
- - - - - Status: status ?> -

title ?>

-

Confirm respondents

- - - - - - - - - -
- - +
+
+ + + + + + + + +

Confirm respondents

+ + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/application/views/users/user_form.php b/application/views/users/user_form.php index e56f8da..393278e 100644 --- a/application/views/users/user_form.php +++ b/application/views/users/user_form.php @@ -1,59 +1,95 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - - - - config->item('roles') as $key => $role_name): ?> - has_role($key) : FALSE; - ?> -
- - +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + config->item('roles') as $key => $role_name): ?> + has_role($key) : FALSE; + ?> +
+ + +
+ + + + + + + +
+ + Notify user about account creation. +
+ + + +
+ + + The password is always needed when changing profiles.
+ + + - - - - - -
- - Notify user about account creation. -
- - - -
- - - The password is always needed when changing profiles.
- - - + - -
\ No newline at end of file + + \ No newline at end of file diff --git a/application/views/users/user_list.php b/application/views/users/user_list.php index 9109333..8c5b32d 100644 --- a/application/views/users/user_list.php +++ b/application/views/users/user_list.php @@ -1,31 +1,64 @@ -
- - - - - - - - - - +
+
-
- - - - - - - - + - -
NameRolesStatusActions
name ?>roles); ?>status; ?> - -
-
\ No newline at end of file +
+
+
+
+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + +
NameRolesStatus
name; ?>roles); ?>status; ?> + + Edit + +
+
+
+
+
+ + + \ No newline at end of file diff --git a/application/views/users/user_profile.php b/application/views/users/user_profile.php index 24bc1c6..5c6764f 100644 --- a/application/views/users/user_profile.php +++ b/application/views/users/user_profile.php @@ -1,9 +1 @@ -
- -

name ?>

- - Edit profile - - - -
\ No newline at end of file + \ No newline at end of file diff --git a/application/views/users/user_recover_password.php b/application/views/users/user_recover_password.php index ce6884d..58ea7f1 100644 --- a/application/views/users/user_recover_password.php +++ b/application/views/users/user_recover_password.php @@ -1,11 +1,30 @@ -
- - + - - - - - - -
\ No newline at end of file +
+
+ +
+
\ No newline at end of file diff --git a/application/views/users/user_reset_password.php b/application/views/users/user_reset_password.php index 43eba5a..1f273bb 100644 --- a/application/views/users/user_reset_password.php +++ b/application/views/users/user_reset_password.php @@ -1,13 +1,32 @@ -
- - + - - - - - - - - -
\ No newline at end of file +
+
+ +
+
\ No newline at end of file diff --git a/assets/fonts/airwolf-icons/airwolf-icons.eot b/assets/fonts/airwolf-icons/airwolf-icons.eot new file mode 100644 index 0000000..de6cdf9 Binary files /dev/null and b/assets/fonts/airwolf-icons/airwolf-icons.eot differ diff --git a/assets/fonts/airwolf-icons/airwolf-icons.svg b/assets/fonts/airwolf-icons/airwolf-icons.svg new file mode 100644 index 0000000..0bb802f --- /dev/null +++ b/assets/fonts/airwolf-icons/airwolf-icons.svg @@ -0,0 +1,68 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/fonts/airwolf-icons/airwolf-icons.ttf b/assets/fonts/airwolf-icons/airwolf-icons.ttf new file mode 100644 index 0000000..58fcb83 Binary files /dev/null and b/assets/fonts/airwolf-icons/airwolf-icons.ttf differ diff --git a/assets/fonts/airwolf-icons/airwolf-icons.woff b/assets/fonts/airwolf-icons/airwolf-icons.woff new file mode 100644 index 0000000..e32cbd6 Binary files /dev/null and b/assets/fonts/airwolf-icons/airwolf-icons.woff differ diff --git a/src/scripts/dropdown_menu.js b/src/scripts/dropdown_menu.js new file mode 100644 index 0000000..8aa6e51 --- /dev/null +++ b/src/scripts/dropdown_menu.js @@ -0,0 +1,66 @@ +$(document).ready(function(){ + // Dropdown for the main sidebar. + $('a[data-dropdown="action-bttn-primary"]').mouseenter(function() { + var $self = $(this); + var $dropdown = $self.siblings('.action-dropdown-primary'); + clearTimeout($dropdown.data('hide_timeout')); + // Hide others. + $('.action-dropdown-primary').not($dropdown).hide(); + + $dropdown.show(); + }) + .mouseleave(function() { + var $self = $(this); + var $dropdown = $self.siblings('.action-dropdown-primary'); + + var hide_timeout = setTimeout(function() { + $dropdown.hide(); + }, 150); + + $dropdown.data('hide_timeout', hide_timeout); + }) + .click(function() { + window.location = $(this).attr('href'); + }); + + $('.action-dropdown-primary').mouseenter(function() { + var $self = $(this); + clearTimeout($self.data('hide_timeout')); + $self.show(); + }) + .mouseleave(function() { + var $self = $(this); + var hide_timeout = setTimeout(function() { + $self.hide(); + }, 150); + $self.data('hide_timeout', hide_timeout); + }); + + // Other dropdowns triggered by buttons. + $('a[data-dropdown="action-bttn"]').click(function(event) { + event.stopPropagation(); + event.preventDefault(); + + var $self = $(this); + var $dropdown = $self.siblings('.action-dropdown'); + + // Hide others. + $('.action-dropdown').not($dropdown).hide(); + $('a[data-dropdown="action-bttn"]').not($self).removeClass('current'); + + if ($dropdown.is(':hidden')) { + $self.addClass('current'); + $dropdown.show(); + } + else { + $self.removeClass('current'); + $dropdown.hide(); + } + }); + + $(document).click(function() { + $('.action-dropdown').hide(); + $('a[data-dropdown="action-bttn"]').removeClass('current'); + }); + +}); \ No newline at end of file diff --git a/src/scripts/scripts.js b/src/scripts/scripts.js index c3c8817..8b0e62b 100644 --- a/src/scripts/scripts.js +++ b/src/scripts/scripts.js @@ -1 +1,5 @@ -$(document).foundation(); \ No newline at end of file +$(document).foundation(); + +$('a.disabled').click(function(e) { + e.preventDefault(); +}); diff --git a/src/styles/_buttons.scss b/src/styles/_buttons.scss new file mode 100644 index 0000000..39ab9b4 --- /dev/null +++ b/src/styles/_buttons.scss @@ -0,0 +1,236 @@ +/* Generic link styles */ +a:link, +a:visited { + color: palette(blue); + @include transform(translate3d(0, 0, 0)); + @include transition-property(opacity, top, left, right); + @include transition-duration(0.2s, 0.1s, 0.2s, 0.2s); + @include transition-timing-function(ease, ease, ease, ease); + @include transition-delay(0, 0, 0, 0); + cursor: pointer; +} + +a:hover, +a:focus { + outline: none; + opacity: 0.7; +} + +a:active { + position: relative; + top: 2px; +} +/* // Generic link styles */ + +/* Base button style */ +.bttn { + display: inline-block; + position: relative; + z-index: 99; + padding: rem-calc(0 12); + border-radius: rem-calc(4); + font-weight: 700; + + &:link, + &:active, + &:visited { + color: white; + } +} + +.bttn-dropdown { + &:after { + @extend .icon-s-arr-down; + margin-left: rem-calc(8); + } +} + +/* Button sizes */ +.bttn-small { + line-height: rem-calc(32); + height: rem-calc(32); + font-size: rem-calc(14); + + &:before, &:after { + font-size: rem-calc(14); + } +} +.bttn-medium { + line-height: rem-calc(40); + height: rem-calc(40); + font-size: rem-calc(16); + + &:before, &:after { + font-size: rem-calc(16); + } +} +/* // Button sizes */ + +/* Button colors */ +.bttn-primary { + @include button-skin(palette(blue)); + + &:active, &.current { + @include button-skin(palette(blue), 'active'); + } +} +.bttn-success { + @include button-skin(palette(green)); + + &:active, &.current { + @include button-skin(palette(green), 'active'); + } +} +.bttn-danger { + @include button-skin(palette(red)); + + &:active, &.current { + @include button-skin(palette(red), 'active'); + } +} +.bttn-default { + @include button-skin(palette(grey, mid-dark), $tint_prc: 10%); + + &:active, &.current { + @include button-skin(palette(grey, mid-dark), $status: 'active', $tint_prc: 10%); + } +} +/* // Button colors */ + +/* Button groups */ +.bttn-group { + @include reset-list; + @include clearfix(); + + &>li { + float: left; + + /* Add spacing between buttons except the last one */ + margin-left: 1px; + &:last-child { + margin: none; + } + + /* Remove border-radius. */ + .bttn { + border-radius: 0; + } + + /* Border on the last */ + &:first-child .bttn { + border-radius: rem-calc(4 0 0 4); + } + &:last-child .bttn { + border-radius: rem-calc(0 4 4 0); + } + } +} + +.bttn-center { + float: left; + clear: left; + position: relative; + left: 50%; + + &>li { + float: left; + position: relative; + right: 50%; + } +} +/* // Button groups */ + +a.disabled { + opacity: 0.4; + cursor: default; + + &:active { + position: static; + top: 0; + } +} + +/* Dropdown */ +.action-dropdown { + min-width: rem-calc(200); + position: absolute; + top: 52px; + left: 0; + z-index: 80; + display: none; + @include border-radius(4px); + @include reset-list; + box-shadow: 0 0 0 2px rgba(palette(grey, dark), 0.1); + background-color: white; + color: #909598; + font-size: rem-calc(14); + + &:before { + @extend .icon-s-triangle-up; + position: absolute; + z-index: 90; + top: -31px; + left: 8px; + color: white; + font-size: 32px; + text-shadow: 0 -3px rgba(palette(grey, dark), 0.1); + line-height: 56px; + } + + li { + position: relative; + z-index: 95; + + /* Logged user is a special element only used in the primary nav */ + &.logged-user, a { + padding: rem-calc(12); + border-bottom: 1px solid palette(grey, x-light); + } + + &.logged-user strong { + display: block; + color: palette(grey, dark); + font-weight: normal; + } + + a { + display: block; + + &:hover { + background-color: palette(blue, x-light); + border-bottom-color: palette(blue, light); + } + + &.danger { + color: palette(red); + &:hover { + background-color: palette(red, x-light); + border-bottom-color: palette(red, light); + } + } + } + + &:last-child a{ + border: none; + } + } +} + +.action-dropdown-primary { + @extend .action-dropdown; + top: 0; + left: 84px; + + &:before { + @extend .icon-s-triangle-left; + top: 0; + left: -19px; + text-shadow: -3px 0 rgba(palette(grey, dark), 0.1); + } +} + +/* Dropdown for button size */ +.for-bttn-small { + top: 45px; +} +/* // Dropdown for button size */ \ No newline at end of file diff --git a/src/styles/_colors.scss b/src/styles/_colors.scss new file mode 100644 index 0000000..9e03810 --- /dev/null +++ b/src/styles/_colors.scss @@ -0,0 +1,44 @@ +/* ========================================================================== + Colors + ========================================================================== */ +/** + * base: #454D53 + * primary: #047998 + * info: lighten 20% primary + * success: #40B19B + * warning: #FFBB00 + * danger: #DA454A + */ +$palettes: ( + grey: ( + x-light: tint(#454D53, 90%), + light: tint(#454D53, 80%), + base: tint(#454D53, 40%), + mid-dark: tint(#454D53, 10%), + dark: #454D53, + x-dark: shade(#454D53, 5%), + ), + blue: ( + x-light: tint(#047998, 90%), + light: tint(#047998, 80%), + mid-light: tint(#047998, 20%), + base: #047998 + ), + green: ( + x-light: tint(#DA454A, 90%), + base: #40B19B + ), + yellow: ( + x-light: tint(#DA454A, 90%), + base: #FFBB00 + ), + red: ( + x-light: tint(#DA454A, 90%), + light: tint(#DA454A, 80%), + base: #DA454A + ), +); + +@function palette($palette, $tone: 'base') { + @return map-get(map-get($palettes, $palette), $tone); +} \ No newline at end of file diff --git a/src/styles/_forms.scss b/src/styles/_forms.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/styles/_helpers.scss b/src/styles/_helpers.scss new file mode 100644 index 0000000..dff0e73 --- /dev/null +++ b/src/styles/_helpers.scss @@ -0,0 +1,44 @@ +/* ========================================================================== + Helpers - extends + custom vars + ========================================================================== */ + +.visually-hidden { + border: 0 none; + clip: rect(0px, 0px, 0px, 0px); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +@mixin button-skin($bgcolor, $status: 'normal', $tint_prc: 25%, $shade_prc: 25%, $bg_prc: 10%) { + $tint-color: tint($bgcolor, $tint_prc); + $shade-color: shade($bgcolor, $shade_prc); + @if $status == 'active' { + background-color: shade($bgcolor, $bg_prc); + box-shadow:inset 0 2px 0 0 $shade-color, inset 0 -2px 0 0 $tint-color; + } + @else { + background-color: $bgcolor; + box-shadow:inset 0 2px 0 0 $tint-color, inset 0 -2px 0 0 $shade-color; + } +} + +@mixin reset-list { + list-style: none; + padding: 0; + margin: 0; +} + +%rounded { + border-radius: 500px; +} + +%truncated { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + overflow: hidden; +} \ No newline at end of file diff --git a/src/styles/_modules.scss b/src/styles/_modules.scss new file mode 100644 index 0000000..66af44a --- /dev/null +++ b/src/styles/_modules.scss @@ -0,0 +1,69 @@ +.contained { + background-color: white; + padding: rem-calc(32); + box-shadow: 0 2px 0 0 rgba(palette(grey, dark), 0.1); + border-radius: rem-calc(4); + + .contained-head, .contained-foot { + position: relative; + @include clearfix(); + background-color: palette(grey, dark); + color: palette(grey); + padding: rem-calc(32); + font-size: rem-calc(14); + } + + .contained-head { + margin: rem-calc(-32 -32 0 -32); + padding: rem-calc(32); + border-radius: rem-calc(4 4 0 0); + color: white; + } + + .contained-foot { + background-color: palette(grey, dark); + margin: rem-calc(0 -32 -32 -32); + border-radius: rem-calc(0 0 4 4); + } + + .contained-body { + margin: rem-calc(0 -32 0 -32); + } + + table { + width: 100%; + margin: 0; + border: none; + + tr { + td:first-child, th:first-child { + padding-left: rem-calc(32); + } + td:last-child, th:last-child { + padding-right: rem-calc(32); + } + } + + thead { + background-color: palette(grey, dark); + font-weight: 700; + + th { + color: palette(grey); + font-size: rem-calc(16); + padding-bottom: rem-calc(20); + } + } + + tbody { + tr { + border-bottom: 1px solid palette(grey, x-light); + background: none; + + &:last-child { + border: none; + } + } + } + } +} diff --git a/src/styles/_settings.scss b/src/styles/_settings.scss index bc1eb45..e922af1 100644 --- a/src/styles/_settings.scss +++ b/src/styles/_settings.scss @@ -1,11 +1,9 @@ -// +// // FOUNDATION SETTINGS // -// This is the default html and body font-size for the base rem value. -// $rem-base: 16px; - -// Allows the use of rem-calc() or lower-bound() in your settings +// Uncomment to use rem-calc() in your settings +@import "foundation/functions"; // $experimental: true; @@ -15,10 +13,13 @@ // Since the typical default browser font-size is 16px, that makes the calculation for grid size. // If you want your base font-size to be different and not have it affect the grid breakpoints, // set $rem-base to $base-font-size and make sure $base-font-size is a px value. -// $base-font-size: 100%; +$base-font-size: 100%; // The $base-line-height is 100% while $base-font-size is 150% -// $base-line-height: 150%; +$base-line-height: 1.5; + +// This is the default html and body font-size for the base rem value. +$rem-base: 16px; // We use this to control whether or not CSS classes come through in the gem files. // $include-html-classes: true; @@ -29,14 +30,14 @@ // $include-html-grid-classes: $include-html-classes; -// $row-width: rem-calc(1000); -// $column-gutter: rem-calc(30); +$row-width: rem-calc(1180); +$column-gutter: rem-calc(32); // $total-columns: 12; // We use these to control various global styles -// $body-bg: #fff; -// $body-font-color: #222; -// $body-font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; +$body-bg: palette(grey, x-light); +$body-font-color: palette(grey, dark); +$body-font-family: 'Open Sans', sans-serif; // $body-font-weight: normal; // $body-font-style: normal; @@ -49,7 +50,7 @@ // $default-float: left; // We use these as default colors throughout -// $primary-color: #008CBA; +// $primary-color: palette(grey); // $secondary-color: #e7e7e7; // $alert-color: #f04124; // $success-color: #43AC6A; @@ -1085,7 +1086,7 @@ // $header-font-family: join("Open Sans", $body-font-family); // $header-font-weight: 300; // $header-font-style: normal; -// $header-font-color: #222; +$header-font-color: palette(grey, dark); // $header-line-height: 1.4; // $header-top-margin: .2rem; // $header-bottom-margin: .5rem; diff --git a/src/styles/_styles.scss b/src/styles/_styles.scss index 888bcf4..e5f34e5 100644 --- a/src/styles/_styles.scss +++ b/src/styles/_styles.scss @@ -1,9 +1,270 @@ -/*Overrides. Remove later*/ -.survey_list .button, -.survey_list form { - margin: 0; +#site-head { + width: rem-calc(80); + background-color: palette(blue); + position: fixed; + top: 0; + left: 0; + bottom: 0; + + /*TODO: Temporary site-title*/ + #site-title { + font-weight: bold; + margin: rem-calc(56 0 32 0); + + a { + display: block; + span{ + display: block; + color: white; + @include rotate(-90deg); + } + } + } + + /* Navigation bar */ + #nav-links { + @include reset-list; + + a { + display: block; + } + + &>li { + margin-bottom: rem-calc(8); + position: relative; + + &>a { + + &.current { + @include button-skin(palette(blue), 'active'); + } + + margin: 0 auto; + color: white; + display: block; + width: rem-calc(56); + line-height: rem-calc(32); + height: rem-calc(56); + text-align: center; + padding: rem-calc(12); + @include border-radius(4px); + + &:before{ + font-size: rem-calc(32); + } + } + } + + .dashboard { + &:before { + @extend .icon-dashboard; + } + } + .surveys { + &:before { + @extend .icon-survey; + } + } + .users { + &:before { + @extend .icon-users; + } + } + .account { + &:before { + @extend .icon-cog; + } + } + } + + #connection-status { + width: rem-calc(80); + height: rem-calc(80); + position: fixed; + left: 0; + bottom: 0; + background-color: #454d53; + } } -.messages ul { - margin-bottom: 0; +/* Site header on pages like login */ +#site-head.horizontal { + width: 100%; + background: none; + position: static; + + /*TODO: Temporary site-title*/ + #site-title a { + text-align: center; + display: block; + span{ + display: block; + color: palette(grey, dark); + @include rotate(0); + } + } } + + + +#site-body { + margin-left: rem-calc(80); + + /* Section header */ + #page-head { + @include clearfix(); + padding-top: rem-calc(32); + padding-bottom: rem-calc(32); + background-color: palette(grey, x-light); + box-shadow: 0 4px 0 0 rgba(palette(grey, dark), 0.1); + + .heading { + @include grid-column(6); + + .hd-xl { + padding: rem-calc(2) 0; + } + } + + /* Secondary navigation (nav) */ + #secondary { + @include grid-column(6); + + .links { + @include reset-list; + float: right; + + &>li { + float: left; + position: relative; + margin-right: rem-calc(8); + + &:last-child { + margin-right: 0; + } + } + + .sector-switcher { + border-right: rem-calc(2) solid palette(grey, light); + margin-right: rem-calc(16); + + &:last-child { + border: none; + } + } + .bttn-sector { + @extend .bttn; + @extend .bttn-medium; + + font-size: rem-calc(20); + + &:link, &:visited { + color: palette(grey, dark); + } + } + } + } + } +} + +/* ========================================================================== + Survey statuses + ========================================================================== */ +%status-indicator-base { + @extend %rounded; + width: rem-calc(12); + height: rem-calc(12); + + /** Explanation margin. + * top|bottom: the margin value + element height = h1 line-height + * right: shadow value + 8px + * left: shadow value + */ + margin: rem-calc(12 14 12 6); + display: block; + float: left; + content: ''; + box-shadow: 0 0 0 6px rgba(palette(grey, dark), 0.1); +} + +.indicator-status-draft { + &:before { + @extend %status-indicator-base; + background-color: palette(blue); + } +} +.indicator-status-open { + &:before { + @extend %status-indicator-base; + background-color: palette(green); + } +} +.indicator-status-closed { + &:before { + @extend %status-indicator-base; + background-color: palette(grey, dark); + } +} +.indicator-status-canceled { + &:before { + @extend %status-indicator-base; + background-color: palette(red); + } +} + +%status-base { + @extend %rounded; + padding: rem-calc(8 12); + display: inline-block; + line-height: rem-calc(14); + font-weight: 700; + background-color: palette(grey, x-light); + color: palette(grey, dark); + + &:before { + @extend %rounded; + width: rem-calc(12); + height: rem-calc(12); + margin: rem-calc(1) rem-calc(8) 0 0; + content: ''; + display: block; + float: left; + } +} + +.status-draft { + @extend %status-base; + &:before { + background-color: palette(blue); + } +} + +.status-open { + @extend %status-base; + &:before { + background-color: palette(green); + } +} + +.status-closed { + @extend %status-base; + &:before { + background-color: palette(grey, dark); + } +} + +.status-canceled { + @extend %status-base; + &:before { + background-color: palette(red); + } +} + + +.login-box { + @include grid-column(6, $offset: 3); + + form { + margin: rem-calc(32 32 0 32); + } +} \ No newline at end of file diff --git a/src/styles/_toasts.scss b/src/styles/_toasts.scss new file mode 100644 index 0000000..7459ab8 --- /dev/null +++ b/src/styles/_toasts.scss @@ -0,0 +1,96 @@ +/* ========================================================================== + Toasts + ========================================================================== */ + /** + * Styling and overrides for the toast plugin. + * https://github.com/akquinet/jquery-toastmessage-plugin + */ +.toast-container { + width: rem-calc(348); + right: rem-calc(24); +} + +.toast-item { + .toast-item-image { + background: none; + display: none; + } + + display: table; + width: 100%; + background: none; + border: none; + font-family: $body-font-family; + padding: 0; + opacity: 1; + + /* Reset on all p */ + p { + margin: 0; + padding: 0; + } + + &>p { + /* Since the :before has position relative we have to compensate + because items with position relative still occupy their original space. + The margin will be: -before size + 16 (to allow some padding) + */ + margin: 0; + padding: rem-calc(16 48 16 16); + border-radius: rem-calc(0 4 4 0); + font-size: rem-calc(14); + background-color: rgba(palette(grey, dark), 0.9); + } + + &:before { + display: table-cell; + width: rem-calc(56); + border-radius: rem-calc(4 0 0 4); + color: rgba(#FFFFFF, 0.5); + font-size: rem-calc(32); + text-align: center; + } + + &:after { + display: block; + position: absolute; + top: rem-calc(16); + right: rem-calc(16); + width: rem-calc(16); + z-index: 999; + color: white; + font-size: rem-calc(16); + @extend .icon-s-cancel; + } + + ul { + margin-bottom: 0; + } + + .toast-item-close { + background: none; + cursor: pointer; + top: rem-calc(16); + right: rem-calc(16); + width: rem-calc(16); + height: rem-calc(16); + z-index: 1000; + } + + &.toast-type-notice:before { + background-color: rgba(palette(blue), 0.9); + @extend .icon-circle-info; + } + &.toast-type-success:before { + background-color: rgba(palette(green), 0.9); + @extend .icon-circle-success; + } + &.toast-type-warning:before { + background-color: rgba(palette(yellow), 0.9); + @extend .icon-circle-warning; + } + &.toast-type-error:before { + background-color: rgba(palette(red), 0.9); + @extend .icon-circle-error; + } +} diff --git a/src/styles/_type.scss b/src/styles/_type.scss new file mode 100644 index 0000000..3b5306c --- /dev/null +++ b/src/styles/_type.scss @@ -0,0 +1,312 @@ +/* ========================================================================== + Icons + ========================================================================== */ +/* Custom icon font generated with Icomoon */ +@font-face { + font-family: 'airwolf-icons'; + src: url('../fonts/airwolf-icons.eot'); +} +@font-face { + font-family: 'airwolf-icons'; + src: url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype'), + url(data:application/font-woff;charset=utf-8;base64,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) format('woff'); + font-weight: normal; + font-style: normal; +} + +.icon, [class^="icon-"], [class*=" icon-"] { + font-family: 'airwolf-icons'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + // line-height: 1; + vertical-align: middle; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-users { + @extend .icon; + content: "\e600"; +} +.icon-user { + @extend .icon; + content: "\e601"; +} +.icon-trash-bin { + @extend .icon; + content: "\e602"; +} +.icon-tick { + @extend .icon; + content: "\e603"; +} +.icon-survey { + @extend .icon; + content: "\e604"; +} +.icon-s-triangle-up { + @extend .icon; + content: "\e605"; +} +.icon-s-triangle-right { + @extend .icon; + content: "\e606"; +} +.icon-s-triangle-left { + @extend .icon; + content: "\e607"; +} +.icon-s-triangle-down { + @extend .icon; + content: "\e608"; +} +.icon-s-tick { + @extend .icon; + content: "\e609"; +} +.icon-spinner { + @extend .icon; + content: "\e60b"; +} +.icon-share { + @extend .icon; + content: "\e60c"; +} +.icon-s-cancel { + @extend .icon; + content: "\e60d"; +} +.icon-s-arr-up { + @extend .icon; + content: "\e60e"; +} +.icon-s-arr-right { + @extend .icon; + content: "\e60f"; +} +.icon-s-arr-left { + @extend .icon; + content: "\e610"; +} +.icon-s-arr-down { + @extend .icon; + content: "\e611"; +} +.icon-s-add { + @extend .icon; + content: "\e612"; +} +.icon-refresh { + @extend .icon; + content: "\e613"; +} +.icon-pencil { + @extend .icon; + content: "\e614"; +} +.icon-mobile { + @extend .icon; + content: "\e615"; +} +.icon-menu { + @extend .icon; + content: "\e616"; +} +.icon-map-marker { + @extend .icon; + content: "\e617"; +} +.icon-map { + @extend .icon; + content: "\e618"; +} +.icon-list { + @extend .icon; + content: "\e619"; +} +.icon-io-outer { + @extend .icon; + content: "\e61b"; +} +.icon-io-inner { + @extend .icon; + content: "\e61c"; +} +.icon-io { + @extend .icon; + content: "\e61d"; +} +.icon-image { + @extend .icon; + content: "\e61e"; +} +.icon-home { + @extend .icon; + content: "\e61f"; +} +.icon-history { + @extend .icon; + content: "\e620"; +} +.icon-feed { + @extend .icon; + content: "\e621"; +} +.icon-expand { + @extend .icon; + content: "\e622"; +} +.icon-email { + @extend .icon; + content: "\e623"; +} +.icon-download { + @extend .icon; + content: "\e624"; +} +.icon-disc-cancel { + @extend .icon; + content: "\e625"; +} +.icon-dashboard { + @extend .icon; + content: "\e626"; +} +.icon-cog { + @extend .icon; + content: "\e627"; +} +.icon-clock { + @extend .icon; + content: "\e628"; +} +.icon-circle-warning { + @extend .icon; + content: "\e629"; +} +.icon-circle-success { + @extend .icon; + content: "\e62a"; +} +.icon-circle-question { + @extend .icon; + content: "\e62b"; +} +.icon-circle-play { + @extend .icon; + content: "\e62c"; +} +.icon-circle-info { + @extend .icon; + content: "\e62d"; +} +.icon-circle-error { + @extend .icon; + content: "\e62e"; +} +.icon-cancel { + @extend .icon; + content: "\e62f"; +} +.icon-arr-up { + @extend .icon; + content: "\e630"; +} +.icon-arr-tail-up { + @extend .icon; + content: "\e631"; +} +.icon-arr-tail-right { + @extend .icon; + content: "\e632"; +} +.icon-arr-tail-left { + @extend .icon; + content: "\e633"; +} +.icon-arr-tail-down { + @extend .icon; + content: "\e634"; +} +.icon-arr-right-last { + @extend .icon; + content: "\e635"; +} +.icon-arr-right { + @extend .icon; + content: "\e636"; +} +.icon-arr-return { + @extend .icon; + content: "\e637"; +} +.icon-arr-left-first { + @extend .icon; + content: "\e638"; +} +.icon-arr-left { + @extend .icon; + content: "\e639"; +} +.icon-arr-down { + @extend .icon; + content: "\e63a"; +} +.icon-add { + @extend .icon; + content: "\e63b"; +} + + +/* ========================================================================== + Headings + ========================================================================== */ +.hd-xl { + $ft-size: 28; + font-size: rem-calc($ft-size); + line-height: ($ft-size + 8) / $ft-size; + font-weight: 700; + letter-spacing: -0.5px; + margin: rem-calc(0 0 16 0); + padding: 0; + + #page-head & { + margin-bottom: 0; + } +} +.hd-l { + $ft-size: 24; + font-size: rem-calc($ft-size); + line-height: ($ft-size + 8) / $ft-size; + font-weight: 700; + letter-spacing: -0.5px; + margin: rem-calc(0 0 16 0); + padding: 0; +} +.hd-m { + $ft-size: 20; + font-size: rem-calc($ft-size); + line-height: ($ft-size + 8) / $ft-size; + font-weight: 700; + letter-spacing: -0.5px; + margin: rem-calc(0 0 16 0); + padding: 0; +} +.hd-s { + $ft-size: 16; + font-size: rem-calc($ft-size); + line-height: ($ft-size + 8) / $ft-size; + font-weight: 700; + margin: rem-calc(0 0 16 0); + padding: 0; + + .contained-head & { + color: inherit; + margin-bottom: 0; + } +} \ No newline at end of file diff --git a/src/styles/main.scss b/src/styles/main.scss index 8292359..550dd96 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -2,6 +2,9 @@ @charset "UTF-8"; @import "compass"; +// Colors are needed in the settings so must be loaded before. +@import "colors"; + // Custom settings @import "settings"; @@ -45,5 +48,12 @@ //"foundation/components/tooltips", "foundation/components/offcanvas", "foundation/components/visibility"; - -@import "styles" + +@import "helpers"; +@import "buttons"; +@import "forms"; +@import "helpers"; +@import "modules"; +@import "toasts"; +@import "type"; +@import "styles"; \ No newline at end of file diff --git a/src/vendor/jquery-toastmessage b/src/vendor/jquery-toastmessage new file mode 160000 index 0000000..6f5d7bf --- /dev/null +++ b/src/vendor/jquery-toastmessage @@ -0,0 +1 @@ +Subproject commit 6f5d7bf781d4e8ec5e1a82aa23728f5307a6e28a diff --git a/tests/phpunit/helpers/HelperTest.php b/tests/phpunit/helpers/HelperTest.php deleted file mode 100644 index 0ae189a..0000000 --- a/tests/phpunit/helpers/HelperTest.php +++ /dev/null @@ -1,28 +0,0 @@ - array('Success message.'), - 'warning' => array(), - 'error' => array() - ); - $this->assertEquals($msg, $expected); - - // After getting the first get the messages should be cleared. - $msg = Status_msg::get(); - $expected = NULL; - $this->assertEquals($msg, $expected); - - } -} - -?> \ No newline at end of file diff --git a/tests/phpunit/helpers/StatusMsgHelperTest.php b/tests/phpunit/helpers/StatusMsgHelperTest.php new file mode 100644 index 0000000..f4d6742 --- /dev/null +++ b/tests/phpunit/helpers/StatusMsgHelperTest.php @@ -0,0 +1,47 @@ + 'success', + 'msg' => 'Success message.', + 'sticky' => FALSE, + 'time' => $time, + ), + array( + 'level' => 'error', + 'msg' => 'Error message.', + 'sticky' => TRUE, + 'time' => $time, + ), + array( + 'level' => 'warning', + 'msg' => 'Warning message.', + 'sticky' => TRUE, + 'time' => $time, + ) + ); + $this->assertEquals($msg, $expected); + + // After getting the first get the messages should be cleared. + $msg = Status_msg::get(); + $expected = NULL; + $this->assertEquals($msg, $expected); + + } +} + +?> \ No newline at end of file