diff --git a/UserJob.php b/UserJob.php index 89af4d2..deffea0 100644 --- a/UserJob.php +++ b/UserJob.php @@ -17,17 +17,9 @@ class UserJob{ global $wpdb; $this->db = $wpdb; $this->table_name = $wpdb->prefix . 'acare_ts'; - - $this->jobs = $this->list_jobs("2019-07-01", "2019-07-14"); } - private function is_staff() - { - $roles = get_userdata($this->user->ID)->roles; - return $roles == 'staff'; - } - - public function list_jobs($start, $finish){ + public function list_jobs_by_staff($start, $finish){ $response = array( 'status'=>'success', 'jobs' => [], @@ -67,6 +59,45 @@ class UserJob{ return $response; } + public function list_jobs_by_client($start, $finish){ + $response = array( + 'status'=>'success', + 'jobs' => [], + 'client_name'=>$this->user->display_name, + 'job_count' => 0. + ); + $sql = "SELECT * FROM $this->table_name WHERE start>='%s' and start <='%s' and client='%s' order by start ASC"; + //$query = $this->db->prepare ($sql, array($start, $finish, $this->user->user_login)); + $query = $this->db->prepare ($sql, array($start, $finish, "593d3253-07d0-40a7-8b8e-1a5df05a56db")); + $jobs = $this->db->get_results($query); + $response['job_count'] = count($jobs); + //$response['sql'] = $query; + if ($this->db->last_error == ""){ + $response['status'] = 'success'; + foreach( $jobs as $s){ + $response['jobs'][] = array( + 'id' => $s->id, + 'tos' => $s->tos, + 'start'=> $s->start, + 'finish'=> $s->finish, + 'rate'=> $s->rate, + 'staff'=> $s->staff, + 'client'=> $s->client, + 'ack' => $s->ack, + 'rating' => (int) $s->rating, + //descriptions + 'rate_str'=> $this->get_rate_str($s->rate), + 'tos_str'=> $this->get_tos_str($s->tos), + 'staff_name' => $this->get_display_name($s->staff), + ); + } + + }else{ + $response['status'] = 'error'; + $response['err'] = $this->db->last_error; + } + return $response; + } private function get_rate_str($earnings_rate_id) { diff --git a/css/feedback_card.css b/css/feedback_card.css new file mode 100644 index 0000000..a06e526 --- /dev/null +++ b/css/feedback_card.css @@ -0,0 +1,142 @@ +@CHARSET "UTF-8"; +.card { + /* Add shadows to create the "card" effect */ + box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); + transition: 0.3s; +} + +/* On mouse-over, add a deeper shadow */ +.card:hover { + box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); +/* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#d6f9ff+0,9ee8fa+100;Blue+3D */ +background: rgb(214,249,255); /* Old browsers */ +background: -moz-linear-gradient(top, rgba(214,249,255,1) 0%, rgba(158,232,250,1) 100%); /* FF3.6-15 */ +background: -webkit-linear-gradient(top, rgba(214,249,255,1) 0%,rgba(158,232,250,1) 100%); /* Chrome10-25,Safari5.1-6 */ +background: linear-gradient(to bottom, rgba(214,249,255,1) 0%,rgba(158,232,250,1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ +filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#d6f9ff', endColorstr='#9ee8fa',GradientType=0 ); /* IE6-9 */ + +} + +/* Add some padding inside the card container */ +.card .container { + padding: 2px 0px; + min-height:300px; + position: relative; +} + +.card .container.rated { + background: url('../img/circle.png') no-repeat center; + background-size: contain; + +} + +.card img{ + width:100%; +} + +.card .ratecontainer{ + display : inline-block; + position : relative; + text-align: center; + width: 100%; + position:absolute; + bottom: 5px; +} + +.card .container p, +.card .container h1{ + margin-top:5px; + margin-bottom:5px; + margin-left:16px; + margin-right:16px; +} +.card .container h1{ + font-weight:900; + min-height:100px; + max-height:100px; +} + +.day_of_week{ + font-weight:900; + font-size: 5em; + text-align: center; + background-color:dimgrey; + color:white; + text-shadow: 0 0 10px #0e0606; +} + +.job_start, +.job_finish { + display:inline-block; + font-weight:900; + width: 50%; + font-size:3em; + background-color:grey; + color:white; + margin:0px; + padding:0px; + text-align:center; + box-shadow:0 0 2px black inset; + text-shadow: 0 0 10px #0e0606; +} + +.job_time{ + box-shadow:0 0 2px white inset; +} + +.job_day_of_week{ + position: absolute; + top: 1px; + color: white; + right: 20px; + font-size: 1em; + font-weight: bolder; + border-left: 1px dotted white; + border-bottom: 1px dotted white; + padding: 0 10px 0 10px; +} + + + +.rate { + float: left; + height: 46px; + padding: 0 10px; + display: block; +} +.rate:not(:checked) > input { + position:absolute; + /* top:-9999px; */ + visibility:hidden; +} +.rate:not(:checked) > label { + float:right; + width:1em; + overflow:hidden; + white-space:nowrap; + cursor:pointer; + font-size: 2.6em; + color:#ccc; +} +.rate:not(:checked) > label:before { + content: '★ '; +} +.rate > input:checked ~ label { + color: #ffc700; +} +.rate:not(:checked) > label:hover, +.rate:not(:checked) > label:hover ~ label { + color: #deb217; +} +.rate > input:checked + label:hover, +.rate > input:checked + label:hover ~ label, +.rate > input:checked ~ label:hover, +.rate > input:checked ~ label:hover ~ label, +.rate > label:hover ~ input:checked ~ label { + color: #c59b08; +} + +.ult_modal-body{ + height:60vh; + width:80vw; +} \ No newline at end of file diff --git a/html/feedback_card.html b/html/feedback_card.html new file mode 100644 index 0000000..df943be --- /dev/null +++ b/html/feedback_card.html @@ -0,0 +1,41 @@ +{{#jobs}} +
+
+
+
+
+
+
+
{{hl_start_date}}
+
{{hl_start}}
{{hl_finish}}
+
{{start_day}}
+
+
+

+ {{staff_name}} +

+

{{start}} {{start_day}}

+

{{finish}} {{finish_day}}

+

{{tos_str}}

+
+
+ + + + + + + + + + +
+
+
+
+
+
+
+
+
+{{/jobs}} \ No newline at end of file diff --git a/img/circle.png b/img/circle.png new file mode 100644 index 0000000..dc7ef16 Binary files /dev/null and b/img/circle.png differ diff --git a/js/feedback_card.js b/js/feedback_card.js new file mode 100644 index 0000000..a44cf35 --- /dev/null +++ b/js/feedback_card.js @@ -0,0 +1,282 @@ +(function ($) { + $(function () { +/*_____________________________________________*/ + var template = $('#bts_feedback_card').html(); + for (var i=0;i<1;i++){ + var data = {id: i}; + var h = Mustache.render(template, data); + $("#workspace").after(h); + } + + $('input').click(function(){ + var d = $(this).closest('div.rate').attr('data-job-id'); + alert('job id =' + d + ' ' + $(this).attr('value')); + }); + + + function get_my_jobs(){ + $.post(bts().ajax_url, { // POST request + _ajax_nonce: bts().nonce, // nonce + action: "list_job_by_client", // action + start: get_start_date(), + finish: get_finish_date(), + }, function(response, status, xhr){ + if (response.status == "success"){ + pre_process(response); + load_client_jobs(response); + }else{ + display_error(response); + } + }); + } + + //add extra info to response.jobs + function pre_process(response) + { + var newjobs = []; + $.each(response.jobs, function(idx, val){ + + val.hl_start_date = get_hl_start_date(val.start); + val.hl_start = get_hl_start(val.start); + val.hl_finish = get_hl_finish(val.finish); + + val.checked_1 = get_checked(1, val.rating); + val.checked_2 = get_checked(2, val.rating); + val.checked_3 = get_checked(3, val.rating); + val.checked_4 = get_checked(4, val.rating); + val.checked_5 = get_checked(5, val.rating); + + if (val.rating >=1 && val.rating<=5){ + val.rated= 'rated'; + } + + val.start_day = get_weekday_name(val.start); + val.finish_day = get_weekday_name(val.finish); + newjobs.push(val); + }); + response.jobs = newjobs; + } + + function get_hl_start_date(start) + { + var s = new Date(start); + dd = s.getDate(); + if (dd< 10) + dd = "0" + dd; + m = s.getMonth() + 1; + return dd +"/" + m; + } + function get_hl_start(start) + { + return get_hh_mm(start); + } + function get_hl_finish(finish) + { + return get_hh_mm(finish); + } + function get_hh_mm(date){ + var s = new Date(date); + var hh = s.getHours(); + var mm = s.getMinutes(); + if (hh<10) + hh = "0" + hh; + if (mm<10) + mm = "0" + mm; + return hh +":"+ mm ; + + } + + function get_checked(idx, rating) + { + if ( idx == rating) + return "checked"; + else + return ""; + } + + function load_client_jobs(response) + { + set_user_name_at_summary(response.client_name); + var template = $('#bts_feedback_card').html(); + var html = Mustache.render(template, response); + $("#workspace").after(html); + } + + function set_user_name_at_summary(name){ + var title = ''; + if ( typeof name != 'undefined' && name != "") + title = name + "'s Schedule"; + else + title = "Service Schedule"; + $('.bts_client_name span').html(title); + } + + function display_error() + { + err_message_box("Network Error", "Your Jobs for this week cannot be loaded, please try again later. For urgent job arrangement please contact Helen directly."); + } + + function get_weekday_name(dateString) + { + var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat']; + var d = new Date(dateString); + var dayName = days[d.getDay()]; + return dayName; + } + + function format_date(date){ + var dd = date.getDate(); + var mm = date.getMonth() + 1; //January is 0! + var hh = date.getHours(); + var ii = date.getMinutes(); + var ss = date.getSeconds(); + var yyyy = date.getFullYear(); + + if (dd < 10) { + dd = '0' + dd; + } + if (mm < 10) { + mm = '0' + mm; + } + if (hh< 10){ + hh = '0' + hh; + } + if (ii < 10){ + ii = '0' + ii; + } + + if (ss <10 ){ + ss = '0' + ss; + } + return yyyy + '-' + mm + '-' +dd + " " +hh +":" + ii + ":" + ss; + } + + function format_date_only(date){ + var dd = date.getDate(); + var mm = date.getMonth() + 1; //January is 0! + var yyyy = date.getFullYear(); + + if (dd < 10) { + dd = '0' + dd; + } + if (mm < 10) { + mm = '0' + mm; + } + return yyyy + '-' + mm + '-' +dd; + } + + function get_this_week_start(){ + var curr = new Date; // get current date + // First day is the day of the month - the day of the week + var first = curr.getDate() - curr.getDay() + 1; //+1 we want Mon as first + var last = first + 6; // last day is the first day + 6 + + var firstday = new Date(curr.setDate(first)); //Mon + firstday.setHours(0,0,0); + return format_date(firstday); + } + + function get_this_week_end(){ + var curr = new Date; // get current date + // First day is the day of the month - the day of the week + var first = curr.getDate() - curr.getDay() + 1; //+1 we want Mon as first + var last = first + 6; // last day is the first day + 6 + var lastday = new Date(curr.setDate(last)); //Sat + lastday.setHours(23,59,59); + return format_date(lastday); + } + + function get_start_date() + { + var b=feedback_card; + if (b.bts_job_start != '') + return b.bts_job_start + " 00:00:00"; + else + return get_this_week_start(); + } + function get_finish_date() + { + var b=feedback_card; + if (b.bts_job_finish != '') + return b.bts_job_finish +" 23:59:59"; + else + return get_this_week_end(); + } + + function info_message_box(title, message) + { + message_box('.job_ok_box', title, message); + } + + function err_message_box(title, message) + { + message_box('.job_error_box', title, message); + } + + function message_box(selector, title,message) + { + set_modal_title(selector, title); + set_modal_message(selector, message); + $(selector + '_trigger').trigger('click'); + } + + function set_modal_title(selector, title) + { + var el = selector + ' .ult_modal-title '; + $(el).html(title); + } + + function set_modal_message(selector, msg) + { + var el = selector + ' .ult_modal-body '; + $(el).html(msg); + } + + + function do_update_ack(id, rating) + { + $.post(bts().ajax_url, { // POST request + _ajax_nonce: bts().nonce, // nonce + action: "client_ack_job", // action + job_id: id, + rating: rating, + }, function(response, status, xhr){ + if (response.status == "success"){ + display_job_ack(id, response.rating); + }else{ + console.warn("%o", response); + clear_job_ack(id); + } + }); + } + + function clear_job_ack(id) + { + display_job_ack(id, 0); //clear it + } + function display_job_ack(id, rating) + { + for (var i=1; i<=5; i++){ + var selector = 'star' + i + '_' + id; + $(selector).prop('checked',(rating == i)); + } + if (rating >=1 && rating <=5){ + $('#container_' + id).addClass('rated'); + }else{ + $('#container_' + id).removeClass('rated'); + } + } + + $(document).on('click', '.card input[type="radio"]', function(){ + var job_id = $(this).closest('div.rate').attr('data-job-id'); + var rating = $(this).attr('value'); + do_update_ack(job_id, rating); + }); + + + get_my_jobs(); + +/*_____________________________________________*/ + }); +})(jQuery); + diff --git a/ts.php b/ts.php index 61d6d74..eebc770 100644 --- a/ts.php +++ b/ts.php @@ -46,6 +46,7 @@ class AcareOffice{ add_shortcode( 'bts_select_client', array($this, 'bts_select_client')); add_shortcode( 'bts_type_of_service', array($this, 'bts_type_of_service')); add_shortcode( 'bts_staff_job_summary', array($this, 'bts_staff_job_summary')); + add_shortcode( 'bts_feedback_card', array($this, 'bts_feedback_card')); //user profile page add_shortcode( 'bts_user_name', array($this,'bts_user_name')); @@ -67,6 +68,13 @@ class AcareOffice{ add_action('wp_ajax_staff_ack_job', array($this,'staff_ack_job' )); add_action('wp_ajax_nopriv_staff_ack_job', array($this,'staff_ack_job' )); + add_action('wp_ajax_list_job_by_client', array($this,'list_job_by_client' )); + add_action('wp_ajax_nopriv_list_job_by_client', array($this,'list_job_by_client' )); + + add_action('wp_ajax_client_ack_job', array($this,'client_ack_job' )); + add_action('wp_ajax_nopriv_client_ack_job', array($this,'client_ack_job' )); + + // hook add_rewrite_rules function into rewrite_rules_array add_filter('rewrite_rules_array', array($this,'my_add_rewrite_rules')); // hook add_query_vars function into query_vars @@ -173,6 +181,12 @@ class AcareOffice{ 'task/([^/]+)/week-([^/]+)/?$' => 'index.php?pagename=task&bts_user_id=$matches[1]&bts_week_id=$matches[2]', 'task/([^/]+)/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=task&bts_user_id=$matches[1]&bts_job_start=$matches[2]&bts_job_finish=$matches[3]', + 'feedback_card/week-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_week_id=$matches[1]', + 'feedback_card/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_job_start=$matches[1]&bts_job_finish=$matches[2]', + 'feedback_card/([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_user_id=$matches[1]', + 'feedback_card/([^/]+)/week-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_user_id=$matches[1]&bts_week_id=$matches[2]', + 'feedback_card/([^/]+)/start-([^/]+)/finish-([^/]+)/?$' => 'index.php?pagename=feedback_card&bts_user_id=$matches[1]&bts_job_start=$matches[2]&bts_job_finish=$matches[3]', + ); $aRules = $aNewRules + $aRules; return $aRules; @@ -190,6 +204,10 @@ class AcareOffice{ break; case 'time-sheets': $this->cauth_time_sheet(); + break; + case 'feedback_card': + $this->cauth_feedback_card(); + break; } } @@ -237,6 +255,43 @@ class AcareOffice{ } } + private function cauth_feedback_card(){ + $login = get_query_var( 'bts_user_id' ); + $this->bts_job_start = get_query_var( 'bts_job_start' ); + $this->bts_job_finish = get_query_var( 'bts_job_finish' ); + $this->bts_week_id = get_query_var('bts_week_id'); + + $redirect_url = $this->get_redirect_url_for_feedback_card(); + + if ($login != "")//perform autologin, and redirect + { + $client = get_user_by('login', $login); + if ($this->is_client($client)){//is valid client; + $current = wp_get_current_user(); + if($current->ID != $client->ID){ + wp_logout(); + wp_set_current_user($client->ID, $client->display_name); //this is a must + wp_set_auth_cookie($client->ID, true);//only with this, wordpress calls login + redirect and lost week-%d + } + } + wp_redirect($redirect_url); + return; + } + + //no auto login is required if reach here. + $current = wp_get_current_user(); + if ($this->is_admin($current)){ + wp_redirect("/time-sheets/"); + return; + } + if (!$this->is_client($current) && ! $this->is_admin($current)) + { + wp_logout(); + wp_redirect("/login/"); + return; + } + } + private function get_week_id() { $week = get_query_var( 'bts_week_id' ); @@ -263,6 +318,15 @@ class AcareOffice{ return '/task/'; } + private function get_redirect_url_for_feedback_card() + { + if ($this->bts_week_id != "") + return "/feedback_card/week-" . $this->bts_week_id . "/"; + if ($this->bts_job_start!="" && $this->bts_job_finish !="") + return "/feedback_card/start-" . $this->bts_job_start . "/finish-" .$this->bts_job_finish . "/"; + return '/feedback_card/'; + } + private function cauth_time_sheet() { @@ -296,6 +360,7 @@ class AcareOffice{ $this->register_bts_js(); $this->register_timesheet_js_css(); $this->register_task_js_css(); + $this->register_feedback_card_js_css(); } private function register_bts_js() { @@ -348,6 +413,29 @@ class AcareOffice{ ) ); } + private function register_feedback_card_js_css() + { + global $pagename; + if ($pagename != 'feedback_card'){ + return; + } + + $this->bts_job_start = get_query_var( 'bts_job_start' ); + $this->bts_job_finish = get_query_var( 'bts_job_finish' ); + $this->bts_week_id = get_query_var('bts_week_id'); + + wp_enqueue_style( 'feedback_card', plugins_url('css/feedback_card.css', __FILE__)); + wp_enqueue_script( 'feedback_card', plugins_url('js/feedback_card.js', __FILE__), array( 'jquery' , 'bts' )); + wp_enqueue_script('mustache', plugins_url('js/mustache.min.js', __FILE__), array('jquery')); + + wp_localize_script('feedback_card','feedback_card',array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce('feedback_card'), + 'week_id' => $this->bts_week_id, + 'bts_job_start' => $this->bts_job_start, + 'bts_job_finish' => $this->bts_job_finish, + ) ); + } public function sync_users() { @@ -370,7 +458,7 @@ class AcareOffice{ $users = get_users(array('role' => 'staff')); foreach ($users as $u){ $n = new UserJob($u->user_login); - $resp = $n->list_jobs("2019-07-22 00:00:00", "2019-07-28 23:59:59"); + $resp = $n->list_jobs_by_staff("2019-07-22 00:00:00", "2019-07-28 23:59:59"); if ($resp['status']=='success' && $resp['job_count'] >0 ){ if( $u->user_login != "9aa3308e-cc19-4c21-a110-f2c6abec4337" ) continue; @@ -460,6 +548,12 @@ class AcareOffice{ "; return $result; } + + public function bts_feedback_card($attr) + { + return $this->template('bts_feedback_card', 'feedback_card.html'); + } + //generate template based on html file private function template($id, $file) { @@ -711,7 +805,7 @@ class AcareOffice{ } public function list_job_by_staff() { - //check_ajax_referer('acaresydney'); + check_ajax_referer('acaresydney'); $start = $_POST['start']; $finish = $_POST['finish']; @@ -719,9 +813,9 @@ class AcareOffice{ //$finish="2019-07-14 23:59:59"; $user = wp_get_current_user();// should be staff; - if ( $this->is_staff($user) ){ + if ( $this->is_staff($user) || $this->is_admin($user) ){ $n = new UserJob($user->user_login); - $response = $n->list_jobs($start, $finish); + $response = $n->list_jobs_by_staff($start, $finish); wp_send_json($response); }else{ $response = array( @@ -734,11 +828,38 @@ class AcareOffice{ wp_die(); } + public function list_job_by_client() + { + check_ajax_referer('acaresydney'); + $start = $_POST['start']; + $finish = $_POST['finish']; + + //$start="2019-07-01 00:00:00"; + //$finish="2019-07-14 23:59:59"; + + $user = wp_get_current_user();// should be staff; + if ( $this->is_client($user) || $this->is_admin($user) ){ + $n = new UserJob($user->user_login); + $response = $n->list_jobs_by_client($start, $finish); + wp_send_json($response); + }else{ + $response = array( + 'status' => 'error', + 'errmsg' => 'invalid access', + 'user' => $user, + ); + wp_send_json($response); + } + wp_die(); + } private function is_staff($user) { return ($user->ID !=0 && in_array('staff', $user->roles)); } - + private function is_client($user) + { + return ($user->ID !=0 && in_array('client', $user->roles)); + } private function is_admin($user) { $allowed_roles = array('administrator', 'acare_owner'); @@ -793,6 +914,36 @@ class AcareOffice{ $err .= $this->db->last_error; } return $err; + } + public function client_ack_job() + { + check_ajax_referer('acaresydney'); + $job_id = $_POST['job_id']; + $rating = $_POST['rating']; + + $response = array( + 'status'=>'success', + ); + $sql= "UPDATE $this->table_name SET rating=%d WHERE id = %d ; "; + $sql= $this->db->prepare($sql, array($rating, $job_id)); + + $result = $this->db->get_results($sql); + $response['rating'] = (int) $rating; + if ($this->db->last_error !='') + { + $response['status']= 'error'; + $response['err_msg']= $this->db->last_error; + $response['rating'] = 0; + } + wp_send_json($response); + } + + public function feedback_url() + { + $users = get_users(array('role'=>'client')); + foreach($users as $u){ + echo sprintf("%s:\t https://acaresydney.com.au/feedback_card/%s/\n", $u->display_name, $u->user_login); + } } } @@ -801,6 +952,7 @@ $bb = new AcareOffice(); if ( defined( 'WP_CLI' ) && WP_CLI ) { \WP_CLI::add_command( 'sync_users', array($bb, 'sync_user_cli')); \WP_CLI::add_command( 'email_jobs', array($bb, 'email_jobs')); + \WP_CLI::add_command( 'feedback_url', array($bb, 'feedback_url')); } //$bb->class_loader();