Implement user state saving for projects overview [SCI-2733]

This commit is contained in:
Oleksii Kriuchykhin 2018-10-10 16:11:52 +02:00
parent 7a591273a3
commit af02f29fb5
18 changed files with 9348 additions and 89247 deletions

View file

@ -30,7 +30,6 @@
//= require jsnetworkx
//= require bootstrap-select
//= require_directory ./sitewide
//= require jquery.dataTables.yadcf
//= require datatables
//= require ajax-bootstrap-select.min
//= require underscore
@ -267,4 +266,4 @@ var HelperModule = (function(){
$(document).on('turbolinks:load', function() {
$(window).trigger('load.bs.select.data-api');
});
})();
})();

View file

@ -31,7 +31,7 @@
var projectsViewFilter = $('.projects-view-filter.active').data('filter');
var projectsViewFilterChanged = false;
var projectsChanged = false;
var projectsViewSort = 'new';
var projectsViewSort = $('#sortMenuDropdown a.disabled').data('sort');
var TABLE;
@ -524,7 +524,7 @@
var TABLE_ID = '#projects-overview-table';
TABLE = $(TABLE_ID).DataTable({
dom: "R<'row'<'col-sm-9-custom toolbar'l><'col-sm-3-custom'f>>tpi",
stateSave: false,
stateSave: true,
processing: true,
serverSide: true,
scrollY: '64vh',
@ -604,14 +604,18 @@
initRowSelection();
initFormSubmitLinks($(this));
},
stateLoadCallback: function() {
// to be implemented
stateLoadCallback: function(settings, callback) {
$.ajax({
url: $(TABLE_ID).data('state-load-source'),
dataType: 'json',
type: 'GET',
success: function(json) {
callback(json.state);
}
});
},
stateSaveCallback: function(settings, data) {
// to be implemented
},
fnInitComplete: function() {
// to be implemented
stateSaveCallback: function() {
// Don't do anything, state will be updated at backend, based on params
}
});

View file

@ -29,8 +29,9 @@ class ProjectsController < ApplicationController
format.json do
@current_team = current_team if current_team
@current_team ||= current_user.teams.first
@projects = ProjectsOverviewService.new(@current_team, current_user)
.project_cards(params)
@projects = ProjectsOverviewService
.new(@current_team, current_user, params)
.project_cards
render json: {
html: render_to_string(
partial: 'projects/index/team_projects.html.erb',
@ -43,6 +44,10 @@ class ProjectsController < ApplicationController
@teams = current_user.teams
# New project for create new project modal
@project = Project.new
view_state =
current_team.current_view_state(current_user)
@current_filter = view_state.state['filter']
@current_sort = view_state.state.dig('cards', 'sort')
load_projects_tree
end
end
@ -54,8 +59,9 @@ class ProjectsController < ApplicationController
format.json do
@current_team = current_team if current_team
@current_team ||= current_user.teams.first
@projects = ProjectsOverviewService.new(@current_team, current_user)
.projects_datatable(params)
@projects = ProjectsOverviewService
.new(@current_team, current_user, params)
.projects_datatable
end
end
end
@ -313,6 +319,16 @@ class ProjectsController < ApplicationController
end
end
def dt_state_load
respond_to do |format|
format.json do
render json: {
state: current_team.current_view_state(current_user).state['table']
}
end
end
end
private
include FirstTimeDataGenerator
@ -338,9 +354,8 @@ class ProjectsController < ApplicationController
def load_projects_tree
if current_user.teams.any?
@current_team = current_team if current_team
@current_team ||= current_user.teams.first
@current_sort = params[:sort].to_s
@current_sort ||= 'new'
@projects_tree = current_user.projects_tree(@current_team, @current_sort)
else
@projects_tree = []

View file

@ -35,11 +35,28 @@ class Team < ApplicationRecord
has_many :reports, inverse_of: :team, dependent: :destroy
has_many :datatables_reports,
class_name: 'Views::Datatables::DatatablesReport'
has_many :view_states, as: :viewable, dependent: :destroy
after_commit do
Views::Datatables::DatatablesReport.refresh_materialized_view
end
def current_view_state(user)
state = view_states.where(user: user).take
state || view_states.create!(user: user, state: default_view_state)
end
def default_view_state
{ filter: 'active',
cards: { sort: 'new' },
table: {
'start': 0,
'length': 10,
'order' => [2, 'asc'],
'time': Time.new.to_i
} }
end
def search_users(query = nil)
a_query = "%#{query}%"
users.where.not(confirmed_at: nil)

11
app/models/view_state.rb Normal file
View file

@ -0,0 +1,11 @@
# frozen_string_literal:true
class ViewState < ApplicationRecord
belongs_to :user
belongs_to :viewable, polymorphic: true
validates :viewable_id, uniqueness: {
scope: %i(viewable_type user_id),
message: :not_unique
}
end

View file

@ -1,17 +1,30 @@
# frozen_string_literal: true
class ProjectsOverviewService
def initialize(team, user)
def initialize(team, user, params)
@team = team
@user = user
@params = params
@view_state = @team.current_view_state(@user)
if @view_state.state['filter'] != @params[:filter] &&
%w(active archived all).include?(@params[:filter])
@view_state.state['filter'] = @params[:filter]
end
end
def project_cards(params)
def project_cards
cards_state = @view_state.state['cards']
records = fetch_records
records = records.where(archived: true) if params[:filter] == 'archived'
records = records.where(archived: false) if params[:filter] == 'active'
return records unless params[:sort]
case params[:sort]
records = records.where(archived: true) if @params[:filter] == 'archived'
records = records.where(archived: false) if @params[:filter] == 'active'
if @params[:sort] &&
cards_state['sort'] != @params[:sort] &&
%w(new old atoz ztoa).include?(@params[:sort])
cards_state['sort'] = @params[:sort]
@view_state.state['cards'] = cards_state
end
@view_state.save! if @view_state.changed?
case cards_state['sort']
when 'new'
records.order(created_at: :desc)
when 'old'
@ -25,15 +38,19 @@ class ProjectsOverviewService
end
end
def projects_datatable(params)
per_page = params[:length] == '-1' ? 10 : params[:length].to_i
page = params[:start] ? (params[:start].to_i / per_page) + 1 : 1
def projects_datatable
table_state = @view_state.state['table']
per_page = @params[:length] == '-1' ? 10 : @params[:length].to_i
table_state['length'] = per_page if table_state['length'] != per_page
page = @params[:start] ? (@params[:start].to_i / per_page) + 1 : 1
records = fetch_dt_records
records = records.where(archived: true) if params[:filter] == 'archived'
records = records.where(archived: false) if params[:filter] == 'active'
search_value = params.dig(:search, :value)
records = records.where(archived: true) if @params[:filter] == 'archived'
records = records.where(archived: false) if @params[:filter] == 'active'
search_value = @params.dig(:search, :value)
records = search(records, search_value) if search_value.present?
sort(records, params).page(page).per(per_page)
records = sort(records).page(page).per(per_page)
@view_state.save! if @view_state.changed?
records
end
private
@ -118,8 +135,9 @@ class ProjectsOverviewService
}
end
def sort(records, params)
order = params[:order]&.values&.first
def sort(records)
order_state = @view_state.state['table']['order'][0]
order = @params[:order]&.values&.first
if order
dir = order[:dir] == 'desc' ? 'DESC' : 'ASC'
column_index = order[:column]
@ -127,6 +145,9 @@ class ProjectsOverviewService
dir = 'ASC'
column_index = '1'
end
if order_state != [column_index.to_i, dir.downcase]
@view_state.state['table']['order'][0] = [column_index.to_i, dir.downcase]
end
sort_column = sortable_columns[column_index]
sort_column ||= sortable_columns['1']
records.order("#{sort_column} #{dir}")

View file

@ -100,17 +100,13 @@
</button>
<ul id="sortMenuDropdown" class="dropdown-menu" aria-labelledby="sortMenu">
<% ["new", "old", "atoz", "ztoa"].each do |sort| %>
<% if @current_sort != sort %>
<li>
<a class="<%= 'disabled' if sort == 'new' %>" href="#" data-sort="<%= sort %>">
<%= t('projects.index.sort_' + sort) %>
</a>
</li>
<% else %>
<li >
<a class="disabled" href="#"><%= t('projects.index.sort_' + sort) %></a>
</li>
<% end %>
<li>
<% if @current_sort != sort %>
<a href="#" data-sort="<%= sort %>"><%= t('projects.index.sort_' + sort) %></a>
<% else %>
<a class="disabled" href="#" data-sort="<%= sort %>"><%= t('projects.index.sort_' + sort) %></a>
<% end %>
</li>
<% end %>
</ul>
</div>

View file

@ -1,6 +1,7 @@
<div class="projects-overview-table">
<table id="projects-overview-table" class="table"
data-source="<%= projects_index_dt_path %>">
data-source="<%= projects_index_dt_path %>"
data-state-load-source="<%= projects_dt_state_load_path %>">
<thead>
<tr>
<th><input name="select_all" value="1" type="checkbox"></th>

View file

@ -19,17 +19,23 @@
<ul class="nav navbar-nav navbar-right" style="vertical-align: bottom">
<% if all_projects_page? %>
<% if can_read_team?(current_team) %>
<li id="projects-active-nav-tab" class="active projects-view-filter" data-filter="active">
<li id="projects-active-nav-tab"
class="<%= 'active' if @current_filter == 'active' %> projects-view-filter"
data-filter="active">
<a href="#" title="<%=t "nav2.all_projects.index" %>">
<span><%=t "nav2.all_projects.index" %></span>
</a>
</li>
<li id="projects-archive-nav-tab" class="projects-view-filter" data-filter="archived">
<li id="projects-archive-nav-tab"
class="<%= 'active' if @current_filter == 'archived' %> projects-view-filter"
data-filter="archived">
<a href="#" title="<%=t "nav2.all_projects.archive" %>">
<span><%=t "nav2.all_projects.archive" %></span>
</a>
</li>
<li id="projects-all-nav-tab" class="projects-view-filter" data-filter="all">
<li id="projects-all-nav-tab"
class="<%= 'active' if @current_filter == 'all' %> projects-view-filter"
data-filter="all">
<a href="#" title="<%=t "nav2.all_projects.all" %>">
<span><%=t "nav2.all_projects.all" %></span>
</a>

View file

@ -59,6 +59,11 @@ en:
attributes:
name:
taken: "This project name has to be unique inside a team (this includes the archive)."
view_state:
attributes:
viewable_id:
not_unique: "State already exists for this user and parent object"
helpers:
label:
team:

View file

@ -189,6 +189,8 @@ Rails.application.routes.draw do
get 'projects/archive', to: 'projects#archive', as: 'projects_archive'
post 'projects/index_dt', to: 'projects#index_dt', as: 'projects_index_dt'
get 'projects/dt_state_load', to: 'projects#dt_state_load',
as: 'projects_dt_state_load'
resources :reports, only: :index
get 'reports/datatable', to: 'reports#datatable'

View file

@ -0,0 +1,13 @@
# frozen_string_literal:true
class CreateViewStates < ActiveRecord::Migration[5.1]
def change
create_table :view_states do |t|
t.jsonb :state
t.references :user, foreign_key: true
t.references :viewable, polymorphic: true
t.timestamps
end
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180524091143) do
ActiveRecord::Schema.define(version: 20181008130519) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -796,6 +796,17 @@ ActiveRecord::Schema.define(version: 20180524091143) do
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
create_table "view_states", force: :cascade do |t|
t.jsonb "state"
t.bigint "user_id"
t.string "viewable_type"
t.bigint "viewable_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_view_states_on_user_id"
t.index ["viewable_type", "viewable_id"], name: "index_view_states_on_viewable_type_and_viewable_id"
end
create_table "wopi_actions", id: :serial, force: :cascade do |t|
t.string "action", null: false
t.string "extension", null: false
@ -959,6 +970,7 @@ ActiveRecord::Schema.define(version: 20180524091143) do
add_foreign_key "user_teams", "users"
add_foreign_key "user_teams", "users", column: "assigned_by_id"
add_foreign_key "users", "teams", column: "current_team_id"
add_foreign_key "view_states", "users"
add_foreign_key "wopi_actions", "wopi_apps"
add_foreign_key "wopi_apps", "wopi_discoveries"
add_foreign_key "zip_exports", "users"

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
FactoryBot.define do
factory :view_state do
state {}
user { User.first || create(:user) }
viewable { Team.first || create(:team) }
end
end

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ViewState, type: :model do
it 'should be of class ViewState' do
expect(subject.class).to eq ViewState
end
describe 'Database table' do
it { should have_db_column :state }
it { should have_db_column :user_id }
it { should have_db_column :viewable_type }
it { should have_db_column :viewable_id }
it { should have_db_column :created_at }
it { should have_db_column :updated_at }
end
describe 'Relations' do
it { should belong_to :user }
it { should belong_to :viewable }
end
describe 'Should be a valid object' do
it { should validate_presence_of :user }
it { should validate_presence_of :viewable }
end
end

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -4,10 +4,10 @@
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
* https://datatables.net/download/#bs/jszip-2.5.0,pdfmake-0.1.18,dt-1.10.8,b-1.0.1,b-colvis-1.0.1,b-flash-1.0.1,b-html5-1.0.1,b-print-1.0.1,cr-1.2.0,r-1.0.7,rr-1.0.0,se-1.0.0
* https://datatables.net/download/#bs/dt-1.10.18/b-1.5.4/b-colvis-1.5.4/b-html5-1.5.4/b-print-1.5.4/cr-1.5.0/r-2.2.2
*
* Included libraries:
* JSZip 2.5.0, pdfmake 0.1.18, DataTables 1.10.8, Buttons 1.0.1, Column visibility 1.0.1, Flash export 1.0.1, HTML5 export 1.0.1, Print view 1.0.1, ColReorder 1.2.0, Responsive 1.0.7, RowReorder 1.0.0, Select 1.0.0
* DataTables 1.10.18, Buttons 1.5.4, Column visibility 1.5.4, HTML5 export 1.5.4, Print view 1.5.4, ColReorder 1.5.0, Responsive 2.2.2
*/
table.dataTable {
@ -15,13 +15,17 @@ table.dataTable {
margin-top: 6px !important;
margin-bottom: 6px !important;
max-width: none !important;
border-collapse: separate !important;
}
table.dataTable td,
table.dataTable th {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
table.dataTable td.dataTables_empty,
table.dataTable th.dataTables_empty {
text-align: center;
}
table.dataTable.nowrap th,
table.dataTable.nowrap td {
white-space: nowrap;
@ -62,9 +66,21 @@ div.dataTables_wrapper div.dataTables_paginate ul.pagination {
margin: 2px 0;
white-space: nowrap;
}
div.dataTables_wrapper div.dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
margin-left: -100px;
margin-top: -26px;
text-align: center;
padding: 1em 0;
}
table.dataTable thead > tr > th,
table.dataTable thead > tr > td {
table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting,
table.dataTable thead > tr > td.sorting_asc,
table.dataTable thead > tr > td.sorting_desc,
table.dataTable thead > tr > td.sorting {
padding-right: 30px;
}
table.dataTable thead > tr > th:active,
@ -113,22 +129,25 @@ div.dataTables_scrollHead table.dataTable {
margin-bottom: 0 !important;
}
div.dataTables_scrollBody table {
div.dataTables_scrollBody > table {
border-top: none;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
div.dataTables_scrollBody table thead .sorting:after,
div.dataTables_scrollBody table thead .sorting_asc:after,
div.dataTables_scrollBody table thead .sorting_desc:after {
div.dataTables_scrollBody > table > thead .sorting:after,
div.dataTables_scrollBody > table > thead .sorting_asc:after,
div.dataTables_scrollBody > table > thead .sorting_desc:after {
display: none;
}
div.dataTables_scrollBody table tbody tr:first-child th,
div.dataTables_scrollBody table tbody tr:first-child td {
div.dataTables_scrollBody > table > tbody > tr:first-child > th,
div.dataTables_scrollBody > table > tbody > tr:first-child > td {
border-top: none;
}
div.dataTables_scrollFoot table {
div.dataTables_scrollFoot > .dataTables_scrollFootInner {
box-sizing: content-box;
}
div.dataTables_scrollFoot > .dataTables_scrollFootInner > table {
margin-top: 0 !important;
border-top: none;
}
@ -151,9 +170,6 @@ table.dataTable.table-condensed .sorting_desc:after {
right: 6px;
}
table.table-bordered.dataTable {
border-collapse: separate !important;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-left-width: 0;
@ -172,7 +188,46 @@ div.dataTables_scrollHead table.table-bordered {
border-bottom-width: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row {
margin: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row > div[class^="col-"]:first-child {
padding-left: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row > div[class^="col-"]:last-child {
padding-right: 0;
}
@keyframes dtb-spinner {
100% {
transform: rotate(360deg);
}
}
@-o-keyframes dtb-spinner {
100% {
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-ms-keyframes dtb-spinner {
100% {
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-webkit-keyframes dtb-spinner {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-moz-keyframes dtb-spinner {
100% {
-moz-transform: rotate(360deg);
transform: rotate(360deg);
}
}
div.dt-button-info {
position: fixed;
top: 50%;
@ -198,6 +253,12 @@ div.dt-button-info > div {
padding: 1em;
}
div.dt-button-collection-title {
text-align: center;
padding: 0.3em 0 0.5em;
font-size: 0.9em;
}
ul.dt-button-collection.dropdown-menu {
display: block;
z-index: 2002;
@ -212,6 +273,7 @@ ul.dt-button-collection.dropdown-menu.fixed {
top: 50%;
left: 50%;
margin-left: -75px;
border-radius: 0;
}
ul.dt-button-collection.dropdown-menu.fixed.two-column {
margin-left: -150px;
@ -253,6 +315,9 @@ ul.dt-button-collection.dropdown-menu.four-column {
-o-column-count: 4;
column-count: 4;
}
ul.dt-button-collection.dropdown-menu .dt-button {
border-radius: 0;
}
div.dt-button-background {
position: fixed;
@ -263,8 +328,48 @@ div.dt-button-background {
z-index: 2001;
}
@media screen and (max-width: 767px) {
div.dt-buttons {
float: none;
width: 100%;
text-align: center;
margin-bottom: 0.5em;
}
div.dt-buttons a.btn {
float: none;
}
}
div.dt-buttons button.btn.processing,
div.dt-buttons div.btn.processing,
div.dt-buttons a.btn.processing {
color: rgba(0, 0, 0, 0.2);
}
div.dt-buttons button.btn.processing:after,
div.dt-buttons div.btn.processing:after,
div.dt-buttons a.btn.processing:after {
position: absolute;
top: 50%;
left: 50%;
width: 16px;
height: 16px;
margin: -8px 0 0 -8px;
box-sizing: border-box;
display: block;
content: ' ';
border: 2px solid #282828;
border-radius: 50%;
border-left-color: transparent;
border-right-color: transparent;
animation: dtb-spinner 1500ms infinite linear;
-o-animation: dtb-spinner 1500ms infinite linear;
-ms-animation: dtb-spinner 1500ms infinite linear;
-webkit-animation: dtb-spinner 1500ms infinite linear;
-moz-animation: dtb-spinner 1500ms infinite linear;
}
table.DTCR_clonedTable {
table.DTCR_clonedTable.dataTable {
position: absolute !important;
background-color: rgba(255, 255, 255, 0.7);
z-index: 202;
}
@ -276,42 +381,47 @@ div.DTCR_pointer {
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child,
table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child {
table.dataTable.dtr-inline.collapsed > tbody > tr > td.child,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.child,
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty {
cursor: default !important;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.child:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.child:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty:before {
display: none !important;
}
table.dataTable.dtr-inline.collapsed > tbody > tr[role="row"] > td:first-child,
table.dataTable.dtr-inline.collapsed > tbody > tr[role="row"] > th:first-child {
position: relative;
padding-left: 30px;
cursor: pointer;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child:before {
top: 8px;
table.dataTable.dtr-inline.collapsed > tbody > tr[role="row"] > td:first-child:before,
table.dataTable.dtr-inline.collapsed > tbody > tr[role="row"] > th:first-child:before {
top: 9px;
left: 4px;
height: 16px;
width: 16px;
height: 14px;
width: 14px;
display: block;
position: absolute;
color: white;
border: 2px solid white;
border-radius: 16px;
text-align: center;
line-height: 14px;
border-radius: 14px;
box-shadow: 0 0 3px #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: 'Courier New', Courier, monospace;
line-height: 14px;
content: '+';
background-color: #337ab7;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td:first-child.dataTables_empty:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th:first-child.dataTables_empty:before {
display: none;
}
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td:first-child:before,
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > th:first-child:before {
content: '-';
background-color: #d33333;
}
table.dataTable.dtr-inline.collapsed > tbody > tr.child td:before {
display: none;
}
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td:first-child,
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th:first-child {
padding-left: 27px;
@ -323,7 +433,8 @@ table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th:first-child:befor
height: 14px;
width: 14px;
border-radius: 14px;
line-height: 12px;
line-height: 14px;
text-indent: 3px;
}
table.dataTable.dtr-column > tbody > tr > td.control,
table.dataTable.dtr-column > tbody > tr > th.control {
@ -342,11 +453,13 @@ table.dataTable.dtr-column > tbody > tr > th.control:before {
position: absolute;
color: white;
border: 2px solid white;
border-radius: 16px;
text-align: center;
line-height: 14px;
border-radius: 14px;
box-shadow: 0 0 3px #444;
box-sizing: content-box;
text-align: center;
text-indent: 0 !important;
font-family: 'Courier New', Courier, monospace;
line-height: 14px;
content: '+';
background-color: #337ab7;
}
@ -361,20 +474,20 @@ table.dataTable > tbody > tr.child {
table.dataTable > tbody > tr.child:hover {
background: transparent !important;
}
table.dataTable > tbody > tr.child ul {
table.dataTable > tbody > tr.child ul.dtr-details {
display: inline-block;
list-style-type: none;
margin: 0;
padding: 0;
}
table.dataTable > tbody > tr.child ul li {
table.dataTable > tbody > tr.child ul.dtr-details > li {
border-bottom: 1px solid #efefef;
padding: 0.5em 0;
}
table.dataTable > tbody > tr.child ul li:first-child {
table.dataTable > tbody > tr.child ul.dtr-details > li:first-child {
padding-top: 0;
}
table.dataTable > tbody > tr.child ul li:last-child {
table.dataTable > tbody > tr.child ul.dtr-details > li:last-child {
border-bottom: none;
}
table.dataTable > tbody > tr.child span.dtr-title {
@ -383,139 +496,70 @@ table.dataTable > tbody > tr.child span.dtr-title {
font-weight: bold;
}
table.dt-rowReorder-float {
position: absolute !important;
opacity: 0.8;
table-layout: static;
outline: 2px solid #337ab7;
outline-offset: -2px;
}
tr.dt-rowReorder-moving {
outline: 2px solid #888;
outline-offset: -2px;
}
body.dt-rowReorder-noOverflow {
overflow-x: hidden;
}
table.dataTable td.reorder {
text-align: center;
cursor: move;
}
table.dataTable tbody > tr.selected,
table.dataTable tbody > tr > .selected {
background-color: #08C;
}
table.dataTable.stripe tbody > tr.odd.selected,
table.dataTable.stripe tbody > tr.odd > .selected, table.dataTable.display tbody > tr.odd.selected,
table.dataTable.display tbody > tr.odd > .selected {
background-color: #0085c7;
}
table.dataTable.hover tbody > tr.selected:hover,
table.dataTable.hover tbody > tr > .selected:hover, table.dataTable.display tbody > tr.selected:hover,
table.dataTable.display tbody > tr > .selected:hover {
background-color: #0083c5;
}
table.dataTable.order-column tbody > tr.selected > .sorting_1,
table.dataTable.order-column tbody > tr.selected > .sorting_2,
table.dataTable.order-column tbody > tr.selected > .sorting_3,
table.dataTable.order-column tbody > tr > .selected, table.dataTable.display tbody > tr.selected > .sorting_1,
table.dataTable.display tbody > tr.selected > .sorting_2,
table.dataTable.display tbody > tr.selected > .sorting_3,
table.dataTable.display tbody > tr > .selected {
background-color: #0085c8;
}
table.dataTable.display tbody > tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody > tr.odd.selected > .sorting_1 {
background-color: #0081c1;
}
table.dataTable.display tbody > tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody > tr.odd.selected > .sorting_2 {
background-color: #0082c2;
}
table.dataTable.display tbody > tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody > tr.odd.selected > .sorting_3 {
background-color: #0083c4;
}
table.dataTable.display tbody > tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody > tr.even.selected > .sorting_1 {
background-color: #0085c8;
}
table.dataTable.display tbody > tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody > tr.even.selected > .sorting_2 {
background-color: #0086ca;
}
table.dataTable.display tbody > tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody > tr.even.selected > .sorting_3 {
background-color: #0087cb;
}
table.dataTable.display tbody > tr.odd > .selected, table.dataTable.order-column.stripe tbody > tr.odd > .selected {
background-color: #0081c1;
}
table.dataTable.display tbody > tr.even > .selected, table.dataTable.order-column.stripe tbody > tr.even > .selected {
background-color: #0085c8;
}
table.dataTable.display tbody > tr.selected:hover > .sorting_1, table.dataTable.order-column.hover tbody > tr.selected:hover > .sorting_1 {
background-color: #007dbb;
}
table.dataTable.display tbody > tr.selected:hover > .sorting_2, table.dataTable.order-column.hover tbody > tr.selected:hover > .sorting_2 {
background-color: #007ebd;
}
table.dataTable.display tbody > tr.selected:hover > .sorting_3, table.dataTable.order-column.hover tbody > tr.selected:hover > .sorting_3 {
background-color: #007fbf;
}
table.dataTable.display tbody > tr:hover > .selected,
table.dataTable.display tbody > tr > .selected:hover, table.dataTable.order-column.hover tbody > tr:hover > .selected,
table.dataTable.order-column.hover tbody > tr > .selected:hover {
background-color: #007dbb;
}
table.dataTable td.select-checkbox {
position: relative;
}
table.dataTable td.select-checkbox:before, table.dataTable td.select-checkbox:after {
display: block;
position: absolute;
top: 1.2em;
left: 50%;
width: 12px;
height: 12px;
div.dtr-modal {
position: fixed;
box-sizing: border-box;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 100;
padding: 10em 1em;
}
table.dataTable td.select-checkbox:before {
content: ' ';
margin-top: -6px;
margin-left: -6px;
div.dtr-modal div.dtr-modal-display {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
width: 50%;
height: 50%;
overflow: auto;
margin: auto;
z-index: 102;
overflow: auto;
background-color: #f5f5f7;
border: 1px solid black;
border-radius: 3px;
border-radius: 0.5em;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
}
table.dataTable tr.selected td.select-checkbox:after {
content: '\2714';
margin-top: -11px;
margin-left: -4px;
div.dtr-modal div.dtr-modal-content {
position: relative;
padding: 1em;
}
div.dtr-modal div.dtr-modal-close {
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
border: 1px solid #eaeaea;
background-color: #f9f9f9;
text-align: center;
text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9;
border-radius: 3px;
cursor: pointer;
z-index: 12;
}
div.dtr-modal div.dtr-modal-close:hover {
background-color: #eaeaea;
}
div.dtr-modal div.dtr-modal-background {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 101;
background: rgba(0, 0, 0, 0.6);
}
div.dataTables_wrapper span.select-info,
div.dataTables_wrapper span.select-item {
margin-left: 0.5em;
}
@media screen and (max-width: 640px) {
div.dataTables_wrapper span.select-info,
div.dataTables_wrapper span.select-item {
margin-left: 0;
display: block;
@media screen and (max-width: 767px) {
div.dtr-modal div.dtr-modal-display {
width: 95%;
}
}
table.dataTable tbody tr.selected,
table.dataTable tbody th.selected,
table.dataTable tbody td.selected {
color: white;
}
table.dataTable tbody tr.selected a,
table.dataTable tbody th.selected a,
table.dataTable tbody td.selected a {
color: #a2d4ed;
div.dtr-bs-modal table.table tr:first-child td {
border-top: none;
}