tickets updates

This commit is contained in:
Harry Bomrah 2015-08-19 02:28:47 +08:00
parent 10014335cb
commit 3434cd9ae8
23 changed files with 1356 additions and 55 deletions

View File

@ -0,0 +1,49 @@
(function(){
document.getElementById("search-form").reset();
$("#serach-btn").on("click",function(){
var el = $(this);
if(el.hasClass("opened")){
$(".search-box input").stop().animate({"width":"0"},function(){
$(".search-box").hide();
el.removeClass("opened");
}).val("");
}else{
$(".search-box").show();
el.addClass("opened");
$(".search-box input").stop().animate({"width":"200px"}).focus();
}
return false;
})
var regex = new RegExp(/[a-z]+:/);
$("#smart-field-select").on("change",function(){
var el = $(this),
old_value = $(".search-box input").val(),
matches = old_value.match(regex);
if(matches != null){
old_value = old_value.replace(matches[0], el.val());
if(el.val() == ""){
old_value = old_value.trim();
}
}else{
old_value = el.val() + " " + old_value;
}
$(".search-box input").val(old_value).focus();
})
var interval = null;
$(".search-box input").on("keyup",function(){
var el = $(this);
clearTimeout(interval);
interval = setTimeout(function(){
var val = el.val();
if(val == ""){
$("#smart-field-select").find("option:eq(0)").prop("selected","selected");
}else{
var matches = val.match(regex);
if(matches != null){
$("#smart-field-select").find('option[value="' + matches[0] + '"]').prop("selected","selected");
}
}
},500);
})
})()

View File

@ -0,0 +1,9 @@
@charset "utf-8";
// colors
$blue: #326cc1;
$light-blue: #dce5e8;
$white: #fff;
// units
$main-width: 1000px;

View File

@ -0,0 +1,433 @@
@charset "utf-8";
#main-wrap .wrap-inner {
padding-top: 20px !important;
}
#new-ticket .no-space {
margin-bottom: 0px;
display: none;
}
#connection-status {
text-align: center;
}
#connection-status span.label {
font-size: 14px;
line-height: 18px;
}
#ticket_loader {
text-align: center;
}
#tickets_list tbody tr {
cursor: pointer;
}
#tickets_list th.sort-field {
cursor: pointer;
}
#ticket_footer_pagination {
text-align: center;
@media screen and (max-width: 50rem) {
text-align: left;
}
}
#ticket_footer_pagination .pagination {
margin: 0 0;
}
.ticket-refresh-btn,
.ticket-create-btn {
margin-top: -14px;
}
.label {
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
}
.ticket-modal {
width: 80%;
height: 700px;
max-width: 1000px;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
.ticket-modal.modal.in {
top: 0;
}
.ticket-modal .modal-body {
max-height: 560px;
}
.ticket-modal .label-value {
width: auto;
}
.search-wrap {
display: inline-block;
}
#serach-btn {
font-size: 20px;
margin-right: 5px;
font-weight: bold;
text-decoration: none;
}
.search-box {
display: none;
}
.search-box input {
height: 14px;
}
@media screen and (max-width: 800px) {
.ticket-modal .control-label {
float: none;
width: auto;
text-align: left;
padding: 0;
margin-bottom: 2px;
}
.ticket-modal .form-horizontal .controls {
margin-left: 0;
}
#new-ticket .no-space {
margin-bottom: 10px;
}
}
// Ticket page
// helper classes
$primary: #2980b9;
$second: #489ad8;
$red: #d95f49;
$yellow: #F28A31;
$green: #69B556;
$dark-blue: #2e3e4f;
$gray: #888;
$light-gray: #edf0f1;
$white: #fff;
$black: #000;
.t-list-unstyled {
list-style: none;
margin: 0;
padding: 0;
}
.t-label {
border-radius: 0.125rem;
display: inline-block;
padding: 4px 0.5rem;
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
}
.t-label-primary {
color: $gray;
background-color: $white;
}
.t-status {
text-transform: uppercase;
border-radius: 0.125rem;
font-size: 0.75rem;
display: inline-block;
padding: 4px 0.375rem;
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
}
.t-status-open {
color: $white;
background-color: $green;
}
.t-status-close {
color: $white;
background-color: $gray;
}
.t-status-in-progress {
color: $white;
background-color: $primary;
}
.t-category {
text-transform: uppercase;
border-radius: 0.125rem;
display: inline-block;
padding: 4px 0.375rem;
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
}
.t-category-primary {
color: $white;
background-color: $yellow;
}
.t-btn {
font-size: 0.8125rem;
color: $gray;
background-color: $white;
}
.t-depth-1 {
margin-left: 1.5625rem;
}
.ticket-breadcrumb {
width: 96%;
margin: auto;
max-width: 980px;
background: none;
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
> li {
text-shadow: none;
}
}
.ticket-wrap {
position: relative;
background-color: $white;
-webkit-border-bottom-right-radius: 0.25rem;
-webkit-border-bottom-left-radius: 0.25rem;
-moz-border-radius-bottomright: 0.25rem;
-moz-border-radius-bottomleft: 0.25rem;
border-bottom-right-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
-webkit-box-shadow: 0 2px 15px 0 rgba(0, 0, 0, .2);
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, .2);
max-width: 1000px;
width: 96%;
margin: 0 auto;
}
.ticket-heading-wrap {
background-color: $primary;
-webkit-border-top-left-radius: 4px;
-webkit-border-top-right-radius: 4px;
-moz-border-radius-topleft: 4px;
-moz-border-radius-topright: 4px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
&:hover {
.ticket-action {
top: 1.4rem;
opacity: 1;
}
}
.ticket-heading {
color: $white;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: normal;
font-size: 1.5rem;
margin: 0;
padding: 12px 4rem 12px 1rem;
}
}
.ticket-meta-wrap {
background-color: $second;
padding: 12px 1rem;
.ticket-meta {
color: #EDF0F1;
float: left;
font-size: 0.8125rem;
}
.ticket-state {
float: right;
}
}
.ticket-action {
position: absolute;
right: 1.4rem;
top: -0.2rem;
opacity: 0;
transition: .3s all;
.dropdown-menu {
top: 120%;
left: auto;
right: 0;
padding: 15px 0;
border: none;
border-radius: 0.125rem;
min-width: 100px;
.t-btn {
i {
margin-right: 0.3125rem;
}
}
> li {
> a {
padding: 3px 0.75rem;
}
}
li {
> a {
&:hover {
background-color: $second;
background-image: none;
}
}
}
li {
> .ticket-delete {
&:hover {
background-color: $red;
}
}
> .ticket-close {
&:hover {
background-color: $yellow;
}
}
}
}
}
.ticket-dropdown-icon {
color: rgba($white, .5);
font-size: 1.2rem;
text-align: center;
padding: 2px 0.3125rem;
border-radius: 0.25rem;
border: 3px solid rgba($white, .5);
transition: .2s all;
&:hover {
color: $white;
border-color: $white;
border-radius: 50%;
}
}
.ticket-content {
padding: 10px 1.8rem 20px;
overflow: auto;
}
.ticket-query {
margin: 15px 0 20px 0;
font-size: 0.9375rem;
}
// response
.ticket-response {
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
border: 1px solid lighten($gray, 40%);
}
.ticket-response-heading {
background-color: $second;
outline: 1px solid $second;
display: block;
padding: 6px 0.625rem;
color: $white;
font-size: 0.9375rem;
margin: 0 0 20px;
}
.ticket-response-heading-icon {
margin-right: 0.5rem;
}
.ticket-response-item {
margin: 35px auto;
width: 90%;
}
.ticket-response-meta {
margin-bottom: 10px;
overflow: hidden;
}
.ticket-response-author {
font-size: 1.25rem;
display: inline-block;
margin-right: 10px;
color: $black;
}
.ticket-response-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
margin-right: 0.3125rem;
}
.ticket-response-created-date {
display: inline-block;
font-size: 0.8125rem;
font-style: italic;
color: $gray;
border-bottom: 1px dotted darken($light-gray, 5%);
}
.ticket-response-content {
border: 1px solid lighten($gray, 40%);
padding: 15px 1.5rem;
border-radius: 0.125rem;
font-size: 0.9375rem;
line-height: 1.6;
overflow: auto;
}
// editor
.ticket-editor {
padding: 10px 1.8rem 20px;
}
// ticket table
.ticket-table {
td {
white-space: nowrap;
}
}
// TODO joshua pleae change it
.ticket-category-select{
border: medium none;
text-decoration: none;
color: $white;
border-radius: 0.125rem;
left: auto;
min-width: 100px;
padding: 15px 0;
right: 0;
top: 8.3%;
}
.ticket-category-dropdown{
color: $white;
text-decoration: none;
&:hover{
color : $white;
text-decoration: none;
}
&:active{
outline:none;
}
&:focus{
outline:none;
}
}
.popover[class*=tour-]{
z-index: 1101;
}
// till here

View File

@ -0,0 +1,38 @@
@charset "utf-8";
@import 'ticket_variables';
.ticket-container {
max-width: $main-width;
width: 96%;
margin: auto;
background-color: $white;
}
.ticket-section-heading {
background-color: $blue;
color: $white;
padding: 10px 14px;
}
.ticket-section-container {
padding: 0 1rem;
margin-bottom: 20px;
}
.ticket-category-title {
background-color: #fff;
}
.ticket-item {
padding: 8px 5px;
border-bottom: 1px solid $light-blue;
}
.ticket-item:first-child {
border-top: 1px solid $light-blue;
}
.ticket-section-view-all {
text-align: right;
margin: 20px 5px 40px;
}

View File

@ -0,0 +1,9 @@
@charset "utf-8";
// colors
$blue: #326cc1;
$light-blue: #dce5e8;
// units
$main-width: 1000px;

View File

@ -1,17 +1,9 @@
/*css for tickets module*/
hr{ hr{
border-color: #761D1D -moz-use-text-color #ffffff; border-color: #8E8E8E -moz-use-text-color #ffffff;
margin-top: 5px; margin-top: 5px;
} }
.ticket{ .ticket{
height: 50px;
margin-bottom: 15px;
box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.4);
background-color: #EFEFEF;
border: 1px #007EBA solid;
border-radius: 5px;
} }
.ticket .ticket-title{ .ticket .ticket-title{
@ -19,3 +11,18 @@ hr{
margin-left: 15px; margin-left: 15px;
line-height: 45px; line-height: 45px;
} }
.category_ticket_wrap .search-wrap{
/*float: left;
margin-top: -43px;
margin-left: 200px;*/
display: inline;
}
.category_ticket_wrap h3{
width: auto;
}
.urgent-container{
/*background-color: #F7EEE2;*/
}

View File

@ -108,14 +108,15 @@ class Admin::TicketsApiController < ApplicationController
response["ticket"]["creator_name"] = ticket.ticket_creater_name response["ticket"]["creator_name"] = ticket.ticket_creater_name
response["ticket"]["creator_id"] = ticket.ticket_creater_id response["ticket"]["creator_id"] = ticket.ticket_creater_id
response["ticket"]["queries"] = [] response["ticket"]["queries"] = []
ticket.ticket_queries.each do |qu| ticket.ticket_queries.asc(:created_at).each do |qu|
r = {"query" => qu.query, "responses" => []} r = {"query" => qu.query, "responses" => []}
r["responses"] = qu.ticket_query_responses.collect do |qr| r["responses"] = qu.ticket_query_responses.asc(:created_at).collect do |qr|
user = User.find(qr.response_by) rescue nil user = User.find(qr.response_by) rescue nil
{ {
"response" => qr.response, "response" => qr.response,
"response_time" => qr.created_at, "response_time" => qr.created_at,
"response_by" => (user.name if !user.nil?) "response_by" => (user.name if !user.nil?),
"avatar" => ("http://" + request.host_with_port + user.member_profile.avatar.thumb.url if !user.member_profile.avatar.nil?)
} }
end end
response["ticket"]["queries"] << r response["ticket"]["queries"] << r
@ -141,6 +142,7 @@ class Admin::TicketsApiController < ApplicationController
t["ticket_type"] = ticket.category.title t["ticket_type"] = ticket.category.title
t["created_at"] = ticket.created_at t["created_at"] = ticket.created_at
t["subject"] = ticket.subject t["subject"] = ticket.subject
t["reopened"] = ticket.reopened?
t t
end end
end end

View File

@ -1,40 +1,324 @@
class Admin::TicketsController < OrbitAdminController class Admin::TicketsController < OrbitAdminController
before_action :set_smart_tags
def index def index
mp = current_user.member_profile rescue nil mp = current_user.member_profile rescue nil
@tickets = {} @tickets = {}
@urgent_tickets = {}
if !mp.nil? if !mp.nil?
authorizations = Authorization.where(:role_id.in => mp.roles.collect{|mm| mm.id}) authorizations = Authorization.where(:role_id.in => mp.roles.collect{|mm| mm.id})
if authorizations.count == 0 if authorizations.count == 0
@categories = ModuleApp.find_by_key("ticket").categories rescue [] @categories = ModuleApp.find_by_key("ticket").categories rescue []
@categories.each do |category| @categories.each do |category|
if params[:status] == "closed" @tickets[category] = Ticket.open.not_urgent.limit(5).asc(:created_at)
@tickets[category] = Ticket.closed.limit(5).desc(:created_at) @urgent_tickets[category] = Ticket.open.all_urgent.asc(:created_at)
else
@tickets[category] = Ticket.open.limit(5).desc(:created_at)
end
end end
else else
@categories = authorizations.collect{|authorization| authorization.category} @categories = authorizations.collect{|authorization| authorization.category}
@categories.each do |category| @categories.each do |category|
if params[:status] == "closed" temp = Ticket.where(:category_id => category.id).open.not_urgent.limit(5).asc(:created_at)
temp = Ticket.where(:category_id => category.id).closed.limit(5).desc(:created_at)
@tickets[category] = temp if temp.count > 0 @tickets[category] = temp if temp.count > 0
temp = Ticket.where(:category_id => category.id).open.all_urgent.asc(:created_at)
@urgent_tickets[category] = temp if temp.count > 0
end
end
end
end
def my_tickets
if params[:type] == "history"
@tickets = Ticket.where(:taken_by => current_user.id).closed.group_by(&:category)
else else
temp = Ticket.where(:category_id => category.id).open.limit(5).desc(:created_at) @tickets = Ticket.where(:taken_by => current_user.id).commenced.desc(:urgent).group_by(&:category)
@tickets[category] = temp if temp.count > 0 end
@categories = @tickets.keys
end
def show
@ticket = Ticket.find(params[:id]) rescue nil
@categories = ModuleApp.find_by_key("ticket").categories
end
def changecategory
ticket = Ticket.find(params[:ticket_id]) rescue nil
category = Category.find(params[:category_id]) rescue nil
if !category.nil?
ticket.category_id = category.id
ticket.save
end
redirect_to admin_ticket_path(ticket.id)
end
def start
ticket = Ticket.find(params[:ticket_id]) rescue nil
ticket.status = "commenced"
ticket.taken_by = current_user.id
ticket.save
redirect_to admin_ticket_path(ticket.id)
end
def close
ticket = Ticket.find(params[:ticket_id]) rescue nil
ticket.status = "closed"
ticket.urgent = false
ticket.save
redirect_to admin_ticket_path(ticket.id)
end
def leave
ticket = Ticket.find(params[:ticket_id]) rescue nil
ticket.status = "open"
ticket.taken_by = nil
ticket.save
redirect_to admin_ticket_path(ticket.id)
end
def toggleurgent
mark_urgent = params[:mark_urgent]
if mark_urgent
ticket = Ticket.find(params[:ticket_id]) rescue nil
if !ticket.nil?
if mark_urgent == "true"
if can_mark_urgent?(ticket)
ticket.urgent = true
ticket.save
response = {"success" => true, "can_make_urgent" => true}
else
response = {"success" => true, "can_make_urgent" => false}
end
else
ticket.urgent = false
ticket.save
response = {"success" => true, "can_make_urgent" => true}
end
else
response = {"success" => false}
end
end
render :json => response.to_json
end
def search
statuses = ["open","commenced","closed"]
if params[:keywords].present?
keywords = params[:keywords]
type = (keywords.is_i? ? (keywords.length == 8 ? "number" : "text") : "text")
if type == "text"
regex = Regexp.new(/[a-z]+:/)
matches = keywords.scan(regex)
if !matches.blank?
smart_field = matches.first
if @smart_search_fields.include?(smart_field)
type = "smart"
keywords = keywords.sub(smart_field,"")
end end
end end
end end
end end
case type
when "number"
@tickets = Ticket.where(:status.in => statuses, :uid => keywords)
when "text"
@tickets = Ticket.where(:status.in => statuses)
@tickets = search_data(@tickets,[:subject, :uid, :status, :site_type, :ticket_creater_id, :ticket_creater_name])
regex = Regexp.new(".*"+keywords+".*", "i")
sites = RegisteredSite.any_of({:title => regex},{:site_domain => regex})
sites.each do |site|
@tickets = (@tickets | site.tickets.where(:status.in => statuses))
end
when "smart"
@tickets = smart_search(statuses,nil,smart_field,keywords)
end
if !@tickets.nil?
@tickets = @tickets.group_by(&:category)
@categories = @tickets.keys
else
@tickets = {}
@categories = []
end
end
def submit_response
tq = TicketQuery.find(params[:ticket_query_id]) rescue nil
if !tq.nil?
response = TicketQueryResponse.new
response.response = params[:ticket_query_response]
response.response_by = current_user.id
response.save
tq.ticket_query_responses << response
render :partial => "response", :object => response
else
render :json => {"success" => true}.to_json
end
end end
def tickets_by_category def tickets_by_category
@table_fields = [:ticket_number, :site_name, :subject, :issued_time, :tags, :status, :taken_by] @table_fields = [:ticket_number, :site_name, :subject, :created_at, :tags, :status, :taken_by, :urgent]
statuses = params["type"] == "history" ? ["closed"] : ["open","commenced"]
@category = Category.find(params[:category_id]) rescue nil @category = Category.find(params[:category_id]) rescue nil
if !@category.nil? if params[:keywords].present?
@tickets = Ticket.where(:category_id => @category.id, :status.in => ["open", "commenced"]).page(params[:page]).per(15).desc(:created_at) keywords = params[:keywords]
type = (keywords.is_i? ? (keywords.length == 8 ? "number" : "text") : "text")
if type == "text"
regex = Regexp.new(/[a-z]+:/)
matches = keywords.scan(regex)
if !matches.blank?
smart_field = matches.first
if @smart_search_fields.include?(smart_field)
type = "smart"
keywords = keywords.sub(smart_field,"")
end end
end end
end
end
if type.nil?
@tickets = Ticket.where(:category_id => @category.id, :status.in => statuses).order_by(sort).page(params[:page]).per(15)
else
case type
when "number"
@tickets = Ticket.where(:category_id => @category.id, :status.in => statuses, :uid => keywords).order_by(sort).page(params[:page]).per(15)
when "text"
@tickets = Ticket.where(:category_id => @category.id, :status.in => statuses).order_by(sort)
@tickets = search_data(@tickets,[:subject, :uid, :status, :site_type, :ticket_creater_id, :ticket_creater_name])
regex = Regexp.new(".*"+keywords+".*", "i")
sites = RegisteredSite.any_of({:title => regex},{:site_domain => regex})
sites.each do |site|
@tickets = (@tickets | site.tickets.where(:category_id => @category.id, :status.in => statuses))
end
@tickets = Kaminari.paginate_array(@tickets).page(params[:page]).per(15)
when "smart"
@tickets = smart_search(statuses,@category,smart_field,keywords)
@tickets = @tickets.order_by(sort) rescue @tickets
@tickets = Kaminari.paginate_array(@tickets).page(params[:page]).per(15)
end
end if !@category.nil?
end
private
def can_mark_urgent?(ticket)
tickets = Ticket.where(:category_id => ticket.category_id, :urgent => true).count
return tickets < 5
end
def set_smart_tags
@smart_search_fields = ["sitename:","domain:","subject:","ticket:","status:","query:","type:","takenby:","time:"]
end
def smart_search(statuses, category = nil, smart_field, keywords)
keywords = keywords.strip.nil? ? keywords : keywords.strip
regexes = keywords.split(/\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/)
regex = Regexp.union(regexes.map{|word| Regexp.new(".*"+word+".*", "i")})
tickets = []
case smart_field
when "sitename:"
sites = RegisteredSite.where(:title => regex)
sites.each do |site|
if category.nil?
tickets = site.tickets.where(:status.in => statuses).limit(25)
else
tickets = site.tickets.where(:category_id => category.id, :status.in => statuses)
end
end
when "domain:"
sites = RegisteredSite.where(:site_domain => regex)
sites.each do |site|
if category.nil?
tickets = site.tickets.where(:status.in => statuses).limit(25)
else
tickets = site.tickets.where(:category_id => category.id, :status.in => statuses)
end
end
when "subject:"
if category.nil?
tickets = Ticket.where(:subject => regex, :status.in => statuses).limit(25)
else
tickets = Ticket.where(:subject => regex, :status.in => statuses, :category_id => category.id)
end
when "ticket:"
if category.nil?
tickets = Ticket.where(:uid => keywords)
else
tickets = Ticket.where(:uid => keywords, :category_id => category.id)
end
when "status:"
if category.nil?
tickets = Ticket.where(:status => keywords).limit(25)
else
tickets = Ticket.where(:status => keywords, :category_id => category.id)
end
when "type:"
category = Category.where(:"title.en" => regex).first rescue nil
if category.nil?
category = Category.where(:"title.zh_tw" => regex).first rescue nil
end
tickets = Ticket.where(:category_id => category.id, :status.in => statuses).limit(25) if !category.nil?
when "time:"
if keywords.include?("..")
times = keywords.split("..")
start_time = times.first
end_time = times.last
start_time = start_time + ".01" if start_time.length <= 7
end_time = end_time + ".#{Time.days_in_month(end_time.split(".").last.to_i, end_time.split(".").first.to_i)}" if end_time.length <= 7
start_date = DateTime.parse(start_time) - 1 rescue nil
end_date = DateTime.parse(end_time) + 1 rescue nil
else
if keywords.length <= 7
start_time = keywords + ".01"
end_time = keywords + ".#{Time.days_in_month(keywords.split(".").last.to_i, keywords.split(".").first.to_i)}"
start_date = DateTime.parse(start_time) - 1 rescue nil
end_date = DateTime.parse(end_time) + 1 rescue nil
else
start_time = keywords
start_date = DateTime.parse(start_time) rescue nil
end_date = DateTime.parse(start_time) + 1 rescue nil
end
end
return tickets if start_date.nil? || end_date.nil?
if category.nil?
tickets = Ticket.where(:created_at => (start_date..end_date), :status.in => statuses).limit(25)
else
tickets = Ticket.where(:created_at => (start_date..end_date), :status.in => statuses, :category_id => category.id)
end
when "takenby:"
profiles = MemberProfile.any_of({:"first_name.en" => regex},{:"first_name.zh_tw" => regex},{:"last_name.en" => regex},{:"last_name.zh_tw" => regex})
profiles.each do |profile|
user_id = profile.user.id
if category.nil?
tickets = (tickets | Ticket.where(:taken_by => user_id, :status.in => statuses))
else
tickets = (tickets | Ticket.where(:taken_by => user_id, :category_id => category.id, :status.in => statuses))
end
end
when "query:"
queries = TicketQuery.where(:query => regex).limit(25)
queries.each do |query|
if category.nil?
tickets << query.ticket if statuses.include?(query.ticket.status) && !tickets.include?(query.ticket)
else
tickets << query.ticket if statuses.include?(query.ticket.status) && query.ticket.category_id == category.id && !tickets.include?(query.ticket)
end
end
if params[:sort].present?
s = Sanitize.clean(params[:sort]).to_sym
if params[:order] == "desc"
tickets = tickets.sort{|a,b| b[s] <=> a[s]}
elsif params[:order] == "asc"
tickets = tickets.sort{|a,b| a[s] <=> b[s]}
end
end
end
tickets
end
end end

View File

@ -9,6 +9,7 @@ class Ticket
field :ticket_creater_id field :ticket_creater_id
field :ticket_creater_name field :ticket_creater_name
field :subject field :subject
field :urgent, type: Boolean, default: false
field :reopened_count, type: Integer, :default => 0 field :reopened_count, type: Integer, :default => 0
field :status, :default => "open" field :status, :default => "open"
field :taken_by, type: BSON::ObjectId field :taken_by, type: BSON::ObjectId
@ -16,6 +17,8 @@ class Ticket
scope :open, ->{ where(status: "open") } scope :open, ->{ where(status: "open") }
scope :closed, ->{ where(status: "closed") } scope :closed, ->{ where(status: "closed") }
scope :commenced, ->{ where(status: "commenced") } scope :commenced, ->{ where(status: "commenced") }
scope :not_urgent, ->{ where(:urgent.in => [false,nil]) }
scope :all_urgent, ->{ where(urgent: true) }
belongs_to :registered_site belongs_to :registered_site
@ -27,5 +30,19 @@ class Ticket
self.reopened_count < 2 self.reopened_count < 2
end end
def is_urgent?
self.urgent
end
def reopened?
self.reopened_count > 0
end
def registered_site_title
self.registered_site.title
end
def registered_site_domain
self.registered_site.site_domain
end
end end

View File

@ -0,0 +1,25 @@
<div class="query ticket-query">
<%= query.query.html_safe %>
</div>
<div class="ticket-response t-depth-1">
<h4 class="ticket-response-heading">
<i class="ticket-response-heading-icon fa fa-commenting"></i>
Response
</h4>
<div <%= @ticket.ticket_queries.count == (query_counter + 1) ? "id=response-container" : "" %>>
<%= render :partial => "response", :collection => query.ticket_query_responses %>
</div>
<% if params[:reopen] != "true" && @ticket.ticket_queries.count == (query_counter + 1) %>
<div id="form_container" class="ticket-editor">
<form method="post" action="/admin/ticket/submit_response" id="reopen-form" for="query-response">
<textarea class="ckeditor" name="ticket_query_response" data-fv-validation="required;" data-fv-messages="Cannot be empty;"></textarea>
<input type="hidden" name="ticket_query_id" value="<%= query.id.to_s %>" />
</form>
<input type="submit" id="reopen-form-submit" class="btn btn-primary" value="Submit" />
</div>
<div id="ticket_loader" style="display:none;">
<%= image_tag("preloader.gif", size: "50") %>
<div>Posting your response.</div>
</div>
<% end %>
</div>

View File

@ -0,0 +1,15 @@
<div class="ticket-response-item">
<div class="ticket-response-meta">
<div class="ticket-response-author">
<% user = User.find(response.response_by) rescue nil %>
<img class="ticket-response-avatar" src="<%= user.member_profile.avatar.thumb.url if !user.member_profile.avatar.nil? %>" />
<span class="ticket-response-name"><%= user.name if !user.nil? %></span>
</div>
<div class="ticket-response-created-date">
<% dt = DateTime.parse(response.created_at.to_s) %> <%= dt.strftime("%d %B %Y - %H:%M") %>
</div>
</div>
<div class="ticket-response-content">
<%= response.response.html_safe %>
</div>
</div>

View File

@ -0,0 +1,18 @@
<div class="search-wrap">
<a href="#" id="serach-btn" style="font-size:20px;"><i class="icons-search"></i></a>
<% if params[:keywords].present? %>
<a href="/admin/tickets" style="font-size:20px;"><i class="icons-cycle"></i></a>
<span>Searched for <b><i><%= params[:keywords] %></i></b></span>
<% end %>
<span class="search-box" style="display:none;">
<form action="/admin/tickets/search" method="get" id="search-form">
<select id="smart-field-select" style="width:100px;">
<option value="">general</option>
<% @smart_search_fields.each do |ssf| %>
<option value="<%= ssf %>"><%= ssf %></option>
<% end %>
</select>
<input type="text" placeholder="Search" style="width:0;" name="keywords" />
</form>
</span>
</div>

View File

@ -1,3 +1,29 @@
<div class="ticket" id="ticket_<%= ticket.id.to_s %>" data-id="<%= ticket.id.to_s %>"> <% return if ticket_counter >= 5 %>
<span class="ticket-title"><%= ticket.subject %></span> <% case ticket.status
when "open"
badge_class = "t-status-open"
when "closed"
badge_class = "t-status-close"
when "commenced"
badge_class = "t-status-in-progress"
end
%>
<div class="ticket ticket-item" id="ticket_<%= ticket.id.to_s %>" data-id="<%= ticket.id.to_s %>">
#<%= ticket.uid %> - <a href="<%= admin_ticket_path(ticket.id) %>"><span class="ticket-title"><%= ticket.subject %></span> </a><a href="http://<%= ticket.registered_site.site_domain %>" target="_blank">(<%= ticket.registered_site.title %>)</a>
<div class="pull-right">
<% if ticket.is_urgent? %>
<span class="label label-danger">Urgent</span>
<% end %>
<span class="label label-info <%= badge_class %>">
<i class="fa fa-eye"></i>
<%= ticket.status %>
</span>
<% if !ticket.taken_by.nil? %>
<span class="ticket-author t-label t-label-primary">
<i class="icon-user"></i>
<% user = User.find(ticket.taken_by) %>
<%= user.name if !user.nil? %>
</span>
<% end %>
</div>
</div> </div>

View File

@ -3,7 +3,7 @@
badge_class = "badge-info" badge_class = "badge-info"
when "closed" when "closed"
badge_class = "badge-inverse" badge_class = "badge-inverse"
when "processing" when "commenced"
badge_class = "badge-success" badge_class = "badge-success"
end end
%> %>
@ -24,6 +24,23 @@
</td> </td>
<td> <td>
<span class="label <%= badge_class %>"><%= ticket_by_c.status %></span> <span class="label <%= badge_class %>"><%= ticket_by_c.status %></span>
<% if (ticket_by_c.status == "open" || ticket_by_c.status == "commenced") && ticket_by_c.reopened? %>
<span class="label badge-success">Reopened</span>
<% end %>
</td>
<td class="span2">
<% if !ticket_by_c.taken_by.nil?
user = User.find(ticket_by_c.taken_by) rescue nil%>
<%= user.name if !user.nil? %>
<% else %>
&nbsp;
<% end %>
</td>
<td class="span1">
<% if ticket_by_c.is_urgent? %>
<span class="badge label badge-important">&nbsp;</span>
<% else %>
<span class="badge label badge-success">&nbsp;</span>
<% end %>
</td> </td>
<td class="span2">&nbsp;</td>
</tr> </tr>

View File

@ -1,13 +1,36 @@
<% content_for :page_specific_css do %> <% content_for :page_specific_css do %>
<%= stylesheet_link_tag "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" %>
<%= stylesheet_link_tag "ticket_index" %>
<%= stylesheet_link_tag "tickets" %> <%= stylesheet_link_tag "tickets" %>
<%= stylesheet_link_tag "ticket" %>
<% end %> <% end %>
<div> <%= render :partial => "search_form" %>
<% @categories.each do |category| %> <div class="ticket-container">
<div> <div class="ticket-section-container">
<h3><%= category.title %></h3> <h3 class="ticket-section-heading">Urgent Tickets</h3>
<hr> <% @categories.each do |category| %>
<%= render :partial => "ticket", :collection => @tickets[category] %> <% if !@urgent_tickets[category].nil? %>
<div class="ticket-section-container">
<h4 class="ticket-category-title"><%= category.title %></h4>
<%= render :partial => "ticket", :collection => @urgent_tickets[category] %>
</div>
<% end %>
<% end %>
</div>
<h3 class="ticket-section-heading">Normal Tickets</h3>
<% @categories.each do |category| %>
<div class="ticket-section-container">
<h4 class="ticket-category-title"><%= category.title %></h4>
<%= render :partial => "ticket", :collection => @tickets[category] %>
<div class="ticket-section-view-all">
<a class="btn btn-primary" href="/admin/tickets/category/<%= category.id.to_s %>">View all tickets from <%= category.title %></a>
</div>
</div> </div>
<div class="pull-right"><a href="/admin/tickets/category/<%= category.id.to_s %>">View all tickets from <%= category.title %></a></div>
<% end %> <% end %>
</div> </div>
<script type="text/javascript" src="/assets/search.js"></script>

View File

@ -0,0 +1,31 @@
<% content_for :page_specific_css do %>
<%= stylesheet_link_tag "tickets" %>
<%= stylesheet_link_tag "ticket" %>
<% end %>
<%= render :partial => "search_form" %>
<div class="pull-right">
<% if params[:type] == "history" %>
<a href="/admin/tickets/my_tickets">View my current tickets</a>
<% else %>
<a href="/admin/tickets/my_tickets?type=history">View my past tickets</a>
<% end %>
</div>
<div>
<% @categories.each do |category| %>
<div>
<h3><%= category.title %></h3>
<hr>
<%= render :partial => "ticket", :collection => @tickets[category] %>
</div>
<div class="pull-right"><a href="/admin/tickets/category/<%= category.id.to_s %>/?keywords=takenby: <%= current_user.name %>">View all tickets from <%= category.title %></a></div>
<br>
<% end %>
</div>
<script type="text/javascript" src="/assets/search.js"></script>

View File

@ -0,0 +1,15 @@
<% content_for :page_specific_css do %>
<%= stylesheet_link_tag "tickets" %>
<%= stylesheet_link_tag "ticket" %>
<% end %>
<%= render :partial => "search_form" %>
<div>
<% @categories.each do |category| %>
<div>
<h3><%= category.title %></h3>
<%= render :partial => "ticket", :collection => @tickets[category] %>
</div>
<div class="pull-right"><a href="/admin/tickets/category/<%= category.id.to_s %>/?<%= request.original_fullpath.split("?")[1] %>">View all tickets from <%= category.title %></a></div>
<% end %>
</div>
<script type="text/javascript" src="/assets/search.js"></script>

View File

@ -1 +1,223 @@
this is show <% content_for :page_specific_css do %>
<%= stylesheet_link_tag "https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" %>
<%= stylesheet_link_tag "ticket" %>
<% end %>
<% content_for :page_specific_javascript do %>
<%= javascript_include_tag "lib/jquery.form.js" %>
<%= javascript_include_tag "validator" %>
<% end %>
<% if !@ticket .nil? %>
<% case @ticket.status
when "open"
badge_class = "t-status-open"
when "closed"
badge_class = "t-status-close"
when "commenced"
badge_class = "t-status-in-progress"
end
%>
<div class="ticket-wrap">
<div class="ticket-header">
<div id="ticket-urgent-marker" class="ticket-heading-wrap" <%= "style=background-color:#C61C2D;" if @ticket.is_urgent? %>>
<h3 class="ticket-heading">#<%= @ticket.uid %> - <%= @ticket.subject[0..50] %>
<% if !@ticket.taken_by.nil? %>
<% user = User.find(@ticket.taken_by) %>
<img src="<%= user.member_profile.avatar.thumb.url if !user.member_profile.avatar.nil? %>" style="border-radius: 50%;height: 30px;margin-right: 0.3125rem;width: 30px;" />
<% end %>
</h3>
<div class="ticket-action">
<div class="ticket-dropdown">
<a class="ticket-dropdown-toggle" data-toggle="dropdown" href="#">
<i class="ticket-dropdown-icon fa fa-angle-down"></i>
</a>
<ul class="t-list-unstyled dropdown-menu" role="menu">
<% if @ticket.status == "open" %>
<li>
<a class="ticket-start t-btn" href="/admin/tickets/<%= @ticket.id.to_s %>/start" data-ticket-id="<%= @ticket.id.to_s %>">
<i class="fa fa-times-circle"></i>
Start
</a>
</li>
<li>
<a class="ticket-close t-btn" href="/admin/tickets/<%= @ticket.id.to_s %>/close" data-ticket-id="<%= @ticket.id.to_s %>">
<i class="fa fa-eye-slash"></i>
Close
</a>
</li>
<% elsif @ticket.status == "commenced" %>
<li>
<a class="ticket-start t-btn" data-confirm="Are you sure?" href="/admin/tickets/<%= @ticket.id.to_s %>/leave" data-ticket-id="<%= @ticket.id.to_s %>">
<i class="fa fa-times-circle"></i>
Leave
</a>
</li>
<li>
<a class="ticket-close t-btn" href="/admin/tickets/<%= @ticket.id.to_s %>/close" data-ticket-id="<%= @ticket.id.to_s %>">
<i class="fa fa-eye-slash"></i>
Close
</a>
</li>
<% elsif @ticket.status == "closed" %>
<% if @ticket.can_be_reopened? %>
<li>
<% if params[:reopen] == "true" %>
<a href="#" class="reopen-back-btn t-btn" >Cancel</a>
<% else %>
<a href="/admin/tickets/<%= @ticket.id.to_s %>?reopen=true" class="t-btn">Reopen</a>
<% end %>
</li>
<% else %>
<li>
This ticket cannot be reopened. Please create a new ticket.
</li>
<% end %>
<% end %>
</ul>
</div>
</div>
</div>
<div class="ticket-meta-wrap clearfix">
<div class="ticket-meta">
<span class="ticket-author t-label t-label-primary">
<i class="icon-user"></i>
<%= !@ticket.ticket_creater_name.nil? ? @ticket.ticket_creater_name : @ticket.ticket_creater_id %>
</span>
<span class="ticket-created-date t-label t-label-primary">
<i class="icon-time"></i>
<% dt = DateTime.parse(@ticket.created_at.to_s) %> <%= dt.strftime("%d %B %Y - %H:%M") %>
</span>
</div>
<div class="ticket-state">
<% if @ticket.status != "closed" %>
<a href="/admin/tickets/<%= @ticket.id.to_s %>/toggleurgent" class="btn btn-danger <%= "active" if @ticket.is_urgent? %>" id="toggle-urgent">Urgent</a>
<% end %>
<span class="ticket-status t-status <%= badge_class %>">
<i class="fa fa-eye"></i>
<%= @ticket.status %>
</span>
<span class="ticket-category t-category t-category-primary">
<a class="ticket-category-dropdown" data-toggle="dropdown" href="#">
<i class="fa fa-th-list"></i>
<%= @ticket.category.title %>
<i class="fa fa-angle-down"></i>
</a>
<ul class="ticket-category-select dropdown-menu" role="menu">
<% @categories.each do |category| %>
<li>
<a class="ticket-start t-btn ticket-category-select-option" href="#" data-category-id="<%= category.id.to_s %>">
<%= category.title %>
</a>
</li>
<% end %>
</ul>
</span>
</div>
</div>
</div>
<div id="query_box" class="ticket-content">
<%= render :partial => "query", :collection => @ticket.ticket_queries %>
</div>
<% if params[:reopen] == "true"%>
<% if @ticket.status == "closed" %>
<div id="form_container" class="ticket-editor">
<form method="post" action="/admin/ticket/reopen" id="reopen-form" for="query">
<textarea class="ckeditor" name="ticket_query" data-fv-validation="required;" data-fv-messages="Cannot be empty;"></textarea>
<input type="hidden" name="ticket_id" value="<%= @ticket.uid %>" />
</form>
<input type="submit" id="reopen-form-submit" class="btn btn-primary" value="Reopen" />
<input type="button" class="btn reopen-back-btn" value="Cancel" />
</div>
<div id="ticket_loader" style="display:none;">
<%= image_tag("preloader.gif", size: "50") %>
<div>Posting your query.</div>
</div>
<% else %>
<h4> Ticket is already opened. </h4>
<% end %>
<% end %>
</div>
<% else %>
<div>
Error in fetching ticket.
</div>
<% end %>
<script type="text/javascript">
var fv = new FormValidator($("#reopen-form"));
$("#reopen-form-submit").on("click",function(){
for (instance in CKEDITOR.instances) {
CKEDITOR.instances[instance].updateElement();
}
if(fv.isFormValidated()){
$("#form_container").hide();
$("#ticket_loader").show();
fv.form.ajaxSubmit({
success : function(data){
setTimeout(function(){
if(fv.form.attr("for") == "query"){
$("#form_container").remove();
window.location.href = window.location.pathname;
}else{
fv.reset();
for (instance in CKEDITOR.instances) {
CKEDITOR.instances[instance].setData("");
}
$("#form_container").show();
$("#response-container").append(data);
}
$("#ticket_loader").hide();
},500)
}
});
}
})
$(".reopen-back-btn").on("click",function(){
for (instance in CKEDITOR.instances) {
CKEDITOR.instances[instance].updateElement();
}
if(fv.form.find("textarea").val() == ""){
window.history.back();
}else if(confirm("Are you sure, you want to discard changes?")){
window.history.back();
}else{
return false;
}
})
$(".ticket-category-select-option").on("click",function(){
var el = $(this);
if(confirm("Are you sure?")){
window.location.href = window.location.pathname + "/changecategory?category_id=" + el.data("category-id");
}
return false;
})
$("#toggle-urgent").on("click",function(){
var el = $(this),
mark_urgent = !el.hasClass("active");
$.ajax({
url : el.attr("href"),
type : "post",
data : {"mark_urgent" : mark_urgent},
dataType : "json"
}).done(function(response){
if(response.success){
if(mark_urgent){
if(response.can_make_urgent){
$("#ticket-urgent-marker").css("background-color","#C61C2D");
el.addClass("active");
}else{
alert("Urgent quota for this category is already over.");
}
}else{
$("#ticket-urgent-marker").css("background-color","#2980b9");
el.removeClass("active");
}
}
})
return false;
})
</script>

View File

@ -1,5 +1,47 @@
<div id="index_table"> <% content_for :page_specific_css do %>
<h3><%= @category.title %></h3> <%= stylesheet_link_tag "tickets" %>
<% end %>
<div class="category_ticket_wrap">
<% if !@category.nil? %>
<div>
<h3>
<%= @category.title %><%= " - history" if params[:type] == "history" %>
<div class="search-wrap">
<a href="#" id="serach-btn" style="font-size:20px;"><i class="icons-search"></i></a>
<% if params[:keywords].present? %>
<a href="/admin/tickets/category/<%= @category.id.to_s %><%= "?type=history" if params[:type] == "history" %>" style="font-size:20px;"><i class="icons-cycle"></i></a>
<span>Searched for <b><i><%= params[:keywords] %></i></b></span>
<% end %>
<span class="search-box" style="display:none;">
<form action="<%= request.original_fullpath %>" method="get" id="search-form">
<select id="smart-field-select" style="width:100px;">
<option value="">general</option>
<% @smart_search_fields.each do |ssf| %>
<option value="<%= ssf %>"><%= ssf %></option>
<% end %>
</select>
<input type="text" placeholder="Search" style="width:0;" name="keywords" />
<% if params[:type] == "history" %>
<input type="hidden" name="type" value="history" />
<% end %>
</form>
</span>
</div>
</h3>
<% h = request.original_fullpath.split("?")[1] %>
<% if params[:type] == "history" %>
<% h = h.gsub("type=history","").chomp("&") %>
<span style="position:relative;margin-top:-30px;" class="pull-right"><a href="/admin/tickets/category/<%= @category.id.to_s %><%= h != ""? "?#{h}" : "" %>">View current</a></span>
<% else %>
<% h = h.nil? ? "type=history" : "#{h}&type=history" %>
<span style="position:relative;margin-top:-30px;" class="pull-right"><a href="/admin/tickets/category/<%= @category.id.to_s %>?<%= h %>">View history</a></span>
<% end %>
</div>
<% else %>
<div>
<h3>Category Not found!</h3>
</div>
<% end %>
<table class="table main-list"> <table class="table main-list">
<thead> <thead>
<tr class="sort-header"> <tr class="sort-header">
@ -9,17 +51,19 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<% if !@tickets.nil? %> <% if !@tickets.nil? && @tickets.count > 0 %>
<%= render :partial => "ticket_by_c", :collection => @tickets %> <%= render :partial => "ticket_by_c", :collection => @tickets %>
<% else %> <% else %>
<tr><td colspan="5">No tickets for <%= @category.title rescue "" %></td></tr> <tr><td colspan="7" style="text-align:center;">No tickets.</td></tr>
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
<%= <% if !@tickets.nil? && @tickets.count > 0 %>
<%=
content_tag :div, class: "bottomnav clearfix" do content_tag :div, class: "bottomnav clearfix" do
content_tag :div, paginate(@tickets), class: "pagination pagination-centered" content_tag :div, paginate(@tickets), class: "pagination pagination-centered"
end end
%> %>
<% end %>
</div> </div>
<script type="text/javascript" src="/assets/search.js"></script>

View File

@ -1,4 +1,5 @@
en: en:
tickets: tickets:
tickets: Tickets tickets: Tickets
my_tickets: My Tickets
all: All all: All

View File

@ -1,4 +1,5 @@
zh_tw: zh_tw:
tickets: tickets:
tickets: Tickets tickets: Tickets
my_tickets: My Tickets
all: All all: All

View File

@ -11,8 +11,17 @@ Rails.application.routes.draw do
get "/xhr/ticket/types", to: 'admin/tickets_api#tickettypes' get "/xhr/ticket/types", to: 'admin/tickets_api#tickettypes'
delete "/xhr/ticket/:ticket_id", to: 'admin/tickets_api#destroy' delete "/xhr/ticket/:ticket_id", to: 'admin/tickets_api#destroy'
namespace :admin do namespace :admin do
get "tickets/search" => 'tickets#search'
post "ticket/submit_response" => 'tickets#submit_response'
get "tickets/category/:category_id" => 'tickets#tickets_by_category' get "tickets/category/:category_id" => 'tickets#tickets_by_category'
resources :tickets get "tickets/my_tickets" => 'tickets#my_tickets'
resources :tickets do
get "start"
get "leave"
get "changecategory"
get "close"
post "toggleurgent"
end
end end
end end

View File

@ -20,6 +20,12 @@ module Tickets
:active_for_action=>{'admin/tickets'=>"index"}, :active_for_action=>{'admin/tickets'=>"index"},
:available_for => 'users' :available_for => 'users'
context_link 'tickets.my_tickets',
:link_path=>"admin_tickets_my_tickets_path" ,
:priority=>2,
:active_for_action=>{'admin/tickets'=>"my_tickets"},
:available_for => 'users'
context_link 'categories', context_link 'categories',
:link_path=>"admin_module_app_categories_path" , :link_path=>"admin_module_app_categories_path" ,
:link_arg=>"{:module_app_id=>ModuleApp.find_by(:key=>'ticket').id}", :link_arg=>"{:module_app_id=>ModuleApp.find_by(:key=>'ticket').id}",