mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-07 21:55:20 +08:00
Merge pull request #2460 from biosistemika/features/dashboard
Features/dashboard
This commit is contained in:
commit
26b687f2e6
48 changed files with 1994 additions and 17 deletions
2
Gemfile
2
Gemfile
|
@ -67,7 +67,7 @@ gem 'down', '~> 5.0'
|
|||
gem 'faker' # Generate fake data
|
||||
gem 'fastimage' # Light gem to get image resolution
|
||||
gem 'httparty', '~> 0.13.1'
|
||||
gem 'i18n-js', '~> 3.0' # Localization in javascript files
|
||||
gem 'i18n-js', '~> 3.6' # Localization in javascript files
|
||||
gem 'jbuilder' # JSON structures via a Builder-style DSL
|
||||
gem 'logging', '~> 2.0.0'
|
||||
gem 'nested_form_fields'
|
||||
|
|
|
@ -281,7 +281,7 @@ GEM
|
|||
multi_xml (>= 0.5.2)
|
||||
i18n (1.6.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-js (3.3.0)
|
||||
i18n-js (3.6.0)
|
||||
i18n (>= 0.6.6)
|
||||
image_processing (1.9.3)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
|
@ -631,7 +631,7 @@ DEPENDENCIES
|
|||
figaro
|
||||
hammerjs-rails
|
||||
httparty (~> 0.13.1)
|
||||
i18n-js (~> 3.0)
|
||||
i18n-js (~> 3.6)
|
||||
image_processing (~> 1.2)
|
||||
jbuilder
|
||||
jquery-rails
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
//= require bootstrap-select
|
||||
//= require_directory ./repository_columns/columns_initializers
|
||||
//= require datatables
|
||||
//= require clndr.min
|
||||
//= require ajax-bootstrap-select.min
|
||||
//= require underscore
|
||||
//= require i18n.js
|
||||
|
@ -45,6 +46,7 @@
|
|||
//= require marvinjslauncher
|
||||
//= require_tree ./repositories/renderers
|
||||
//= require_directory ./repositories/validators
|
||||
//= require_directory ./dashboard
|
||||
//= require_directory ./sitewide
|
||||
//= require turbolinks
|
||||
|
||||
|
|
88
app/assets/javascripts/dashboard/calendar.js
Normal file
88
app/assets/javascripts/dashboard/calendar.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
/* global I18n */
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
|
||||
var DasboardCalendarWidget = (function() {
|
||||
function calendarTemplate() {
|
||||
return `<script id="calendar-template" type="text/template">
|
||||
<div class="controls">
|
||||
<div class="clndr-previous-button">
|
||||
<div class="btn btn-light icon-btn"><i class="fas fa-angle-double-left"></i></div>
|
||||
</div>
|
||||
<div class="clndr-title"><%= month %> <%= year %></div>
|
||||
<div class="clndr-next-button">
|
||||
<div class="btn btn-light icon-btn"><i class="fas fa-angle-double-right"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="days-container">
|
||||
<% _.each(daysOfTheWeek, function(day) { %>
|
||||
<div class="day-header"><%= day %></div>
|
||||
<% }); %>
|
||||
<% _.each(days, function(day) { %>
|
||||
<% if (day.classes.includes('event')){ %>
|
||||
<div class="<%= day.classes %>" id="<%= day.id %>">
|
||||
<div class="event-day" data-toggle="dropdown"><%= day.day %></div>
|
||||
<div class="dropdown-menu events-container dropdown-menu-right" role="menu">
|
||||
<div class="title">${I18n.t('dashboard.calendar.due_on')} <%= day.date.format(formatJS) %></div>
|
||||
<div class="tasks"></div>
|
||||
</div>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="<%= day.classes %>" id="<%= day.id %>"><%= day.day %></div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</div>
|
||||
</script>`;
|
||||
}
|
||||
|
||||
function getMonthEventsList(date, clndrInstance) {
|
||||
var getUrl = $('.dashboard-calendar').data('month-events-url');
|
||||
$.get(getUrl, { date: date }, function(result) {
|
||||
clndrInstance.setEvents(result.events);
|
||||
});
|
||||
}
|
||||
|
||||
function initCalendar() {
|
||||
var dayOfWeek = [
|
||||
I18n.t('dashboard.calendar.dow.su'),
|
||||
I18n.t('dashboard.calendar.dow.mo'),
|
||||
I18n.t('dashboard.calendar.dow.tu'),
|
||||
I18n.t('dashboard.calendar.dow.we'),
|
||||
I18n.t('dashboard.calendar.dow.th'),
|
||||
I18n.t('dashboard.calendar.dow.fr'),
|
||||
I18n.t('dashboard.calendar.dow.sa')
|
||||
];
|
||||
var clndrInstance = $('.dashboard-calendar').clndr({
|
||||
template: $(calendarTemplate()).html(),
|
||||
daysOfTheWeek: dayOfWeek,
|
||||
forceSixRows: true,
|
||||
clickEvents: {
|
||||
click: function(target) {
|
||||
var getDayUrl = $('.dashboard-calendar').data('day-events-url');
|
||||
if ($(target.element).hasClass('event')) {
|
||||
$.get(getDayUrl, { date: target.date._i }, function(result) {
|
||||
$(target.element).find('.tasks').html(result.html);
|
||||
});
|
||||
}
|
||||
},
|
||||
onMonthChange: function(month) {
|
||||
getMonthEventsList(month._d, clndrInstance);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
getMonthEventsList((new Date()), clndrInstance);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
if ($('.current-tasks-widget').length) {
|
||||
initCalendar();
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
$(document).on('turbolinks:load', function() {
|
||||
DasboardCalendarWidget.init();
|
||||
});
|
188
app/assets/javascripts/dashboard/current_tasks.js
Normal file
188
app/assets/javascripts/dashboard/current_tasks.js
Normal file
|
@ -0,0 +1,188 @@
|
|||
/* global dropdownSelector I18n animateSpinner PerfectSb InfiniteScroll */
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
var DasboardCurrentTasksWidget = (function() {
|
||||
var sortFilter = '.curent-tasks-filters .sort-filter';
|
||||
var viewFilter = '.curent-tasks-filters .view-filter';
|
||||
var projectFilter = '.curent-tasks-filters .project-filter';
|
||||
var experimentFilter = '.curent-tasks-filters .experiment-filter';
|
||||
var emptyState = `<div class="no-tasks">
|
||||
<p class="text-1">${ I18n.t('dashboard.current_tasks.no_tasks.text_1') }</p>
|
||||
<p class="text-2">${ I18n.t('dashboard.current_tasks.no_tasks.text_2') }</p>
|
||||
<i class="fas fa-angle-double-down"></i>
|
||||
</div>`;
|
||||
|
||||
function generateTasksListHtml(json, container) {
|
||||
$.each(json.data, (i, task) => {
|
||||
var currentTaskItem = ` <a class="current-task-item" href="${task.link}">
|
||||
<div class="current-task-breadcrumbs">${task.project}<span class="slash">/</span>${task.experiment}</div>
|
||||
<div class="item-row">
|
||||
<div class="task-name">${task.name}</div>
|
||||
<div class="task-due-date ${task.state.class} ${task.due_date ? '' : 'hidden'}">
|
||||
<i class="fas fa-calendar-day"></i> ${I18n.t('dashboard.current_tasks.due_date', { date: task.due_date })}
|
||||
</div>
|
||||
<div class="task-progress-container ${task.state.class}">
|
||||
<div class="task-progress" style="padding-left: ${task.steps_precentage}%"></div>
|
||||
<div class="task-progress-label">${task.state.text}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>`;
|
||||
$(container).append(currentTaskItem);
|
||||
});
|
||||
}
|
||||
|
||||
function initInfiniteScroll() {
|
||||
InfiniteScroll.init('.current-tasks-list', {
|
||||
url: $('.current-tasks-list').data('tasksListUrl'),
|
||||
customResponse: (json, container) => {
|
||||
generateTasksListHtml(json, container);
|
||||
},
|
||||
customParams: (params) => {
|
||||
params.project_id = dropdownSelector.getValues(projectFilter);
|
||||
params.experiment_id = dropdownSelector.getValues(experimentFilter);
|
||||
params.sort = dropdownSelector.getValues(sortFilter);
|
||||
params.view = dropdownSelector.getValues(viewFilter);
|
||||
params.query = $('.current-tasks-widget .task-search-field').val();
|
||||
params.mode = $('.current-tasks-navbar .active').data('mode');
|
||||
return params;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadCurrentTasksList(newList) {
|
||||
var $currentTasksList = $('.current-tasks-list');
|
||||
var params = {
|
||||
project_id: dropdownSelector.getValues(projectFilter),
|
||||
experiment_id: dropdownSelector.getValues(experimentFilter),
|
||||
sort: dropdownSelector.getValues(sortFilter),
|
||||
view: dropdownSelector.getValues(viewFilter),
|
||||
query: $('.current-tasks-widget .task-search-field').val(),
|
||||
mode: $('.current-tasks-navbar .active').data('mode')
|
||||
};
|
||||
animateSpinner($currentTasksList, true);
|
||||
$.get($currentTasksList.data('tasksListUrl'), params, function(result) {
|
||||
$currentTasksList.find('.current-task-item, .no-tasks').remove();
|
||||
// Toggle empty state
|
||||
if (result.data.length === 0) {
|
||||
$currentTasksList.append(emptyState);
|
||||
}
|
||||
generateTasksListHtml(result, $currentTasksList);
|
||||
PerfectSb().update_all();
|
||||
if (newList) InfiniteScroll.resetScroll('.current-tasks-list');
|
||||
animateSpinner($currentTasksList, false);
|
||||
});
|
||||
}
|
||||
|
||||
function initFilters() {
|
||||
$('.curent-tasks-filters .clear-button').click((e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
dropdownSelector.selectValue(sortFilter, 'date_asc');
|
||||
dropdownSelector.selectValue(viewFilter, 'uncompleted');
|
||||
dropdownSelector.clearData(projectFilter);
|
||||
dropdownSelector.clearData(experimentFilter);
|
||||
});
|
||||
|
||||
dropdownSelector.init(sortFilter, {
|
||||
noEmptyOption: true,
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
selectAppearance: 'simple',
|
||||
disableSearch: true
|
||||
});
|
||||
|
||||
dropdownSelector.init(viewFilter, {
|
||||
noEmptyOption: true,
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
selectAppearance: 'simple',
|
||||
disableSearch: true
|
||||
});
|
||||
|
||||
dropdownSelector.init(projectFilter, {
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
emptyOptionAjax: true,
|
||||
selectAppearance: 'simple',
|
||||
ajaxParams: (params) => {
|
||||
params.mode = $('.current-tasks-navbar .active').data('mode');
|
||||
return params;
|
||||
},
|
||||
onChange: () => {
|
||||
var selectedValue = dropdownSelector.getValues(projectFilter);
|
||||
if (selectedValue > 0) {
|
||||
dropdownSelector.enableSelector(experimentFilter);
|
||||
} else {
|
||||
dropdownSelector.disableSelector(experimentFilter);
|
||||
}
|
||||
dropdownSelector.clearData(experimentFilter);
|
||||
}
|
||||
});
|
||||
|
||||
dropdownSelector.init(experimentFilter, {
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
emptyOptionAjax: true,
|
||||
selectAppearance: 'simple',
|
||||
ajaxParams: (params) => {
|
||||
params.mode = $('.current-tasks-navbar .active').data('mode');
|
||||
params.project_id = dropdownSelector.getValues(projectFilter);
|
||||
return params;
|
||||
}
|
||||
});
|
||||
|
||||
$('.curent-tasks-filters').click((e) => {
|
||||
// Prevent filter window close
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
dropdownSelector.closeDropdown(sortFilter);
|
||||
dropdownSelector.closeDropdown(viewFilter);
|
||||
dropdownSelector.closeDropdown(projectFilter);
|
||||
dropdownSelector.closeDropdown(experimentFilter);
|
||||
});
|
||||
|
||||
$('.curent-tasks-filters .apply-filters').click((e) => {
|
||||
$('.curent-tasks-filters').dropdown('toggle');
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
loadCurrentTasksList(true);
|
||||
});
|
||||
|
||||
$('.filter-container').on('hide.bs.dropdown', () => {
|
||||
loadCurrentTasksList(true);
|
||||
});
|
||||
}
|
||||
|
||||
function initNavbar() {
|
||||
$('.navbar-assigned, .navbar-all').on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$('.current-tasks-navbar').find('a').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
loadCurrentTasksList(true);
|
||||
});
|
||||
}
|
||||
|
||||
function initSearch() {
|
||||
$('.current-tasks-widget').on('change', '.task-search-field', () => {
|
||||
loadCurrentTasksList();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
if ($('.current-tasks-widget').length) {
|
||||
initNavbar();
|
||||
initFilters();
|
||||
initSearch();
|
||||
loadCurrentTasksList();
|
||||
initInfiniteScroll();
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
$(document).on('turbolinks:load', function() {
|
||||
DasboardCurrentTasksWidget.init();
|
||||
});
|
118
app/assets/javascripts/dashboard/quick_start.js
Normal file
118
app/assets/javascripts/dashboard/quick_start.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
/* global I18n dropdownSelector */
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
var DasboardQuickStartWidget = (function() {
|
||||
var projectFilter = '#create-task-modal .project-filter';
|
||||
var experimentFilter = '#create-task-modal .experiment-filter';
|
||||
var createTaskButton = '#create-task-modal .create-task-button';
|
||||
var newProjectsVisibility = '#create-task-modal .new-projects-visibility';
|
||||
|
||||
function initNewTaskModal() {
|
||||
$('.quick-start-widget .new-task').click(() => {
|
||||
$('#create-task-modal').modal('show');
|
||||
$('#create-task-modal .select-block').attr('data-error', '');
|
||||
});
|
||||
|
||||
dropdownSelector.init(projectFilter, {
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
selectAppearance: 'simple',
|
||||
optionLabel: (data) => {
|
||||
if (data.value === 0) {
|
||||
return `<i class="fas fa-plus"></i>
|
||||
<span class="create-new">${I18n.t('dashboard.create_task_modal.filter_create_new')}</span>
|
||||
<span>"${data.label}"</span>`;
|
||||
}
|
||||
return data.label;
|
||||
},
|
||||
onSelect: () => {
|
||||
var selectedValue = dropdownSelector.getValues(projectFilter);
|
||||
// Toggle project visibility button
|
||||
if (selectedValue === '0') {
|
||||
$(newProjectsVisibility).show();
|
||||
} else {
|
||||
$(newProjectsVisibility).hide();
|
||||
}
|
||||
// Enable/disable experiment filter
|
||||
if (selectedValue >= 0) {
|
||||
dropdownSelector.enableSelector(experimentFilter);
|
||||
} else {
|
||||
dropdownSelector.disableSelector(experimentFilter);
|
||||
}
|
||||
dropdownSelector.clearData(experimentFilter);
|
||||
}
|
||||
});
|
||||
|
||||
dropdownSelector.init(experimentFilter, {
|
||||
singleSelect: true,
|
||||
closeOnSelect: true,
|
||||
selectAppearance: 'simple',
|
||||
optionLabel: (data) => {
|
||||
if (data.value === 0) {
|
||||
return `<i class="fas fa-plus"></i>
|
||||
<span class="create-new">${I18n.t('dashboard.create_task_modal.filter_create_new')}</span>
|
||||
<span>"${data.label}"</span>`;
|
||||
}
|
||||
return data.label;
|
||||
},
|
||||
ajaxParams: (params) => {
|
||||
if (dropdownSelector.getValues(projectFilter) === '0') {
|
||||
params.project = {
|
||||
name: dropdownSelector.getData(projectFilter)[0].label,
|
||||
visibility: $('input[name="projects-visibility-selector"]:checked').val()
|
||||
};
|
||||
} else {
|
||||
params.project = { id: dropdownSelector.getValues(projectFilter) };
|
||||
}
|
||||
return params;
|
||||
},
|
||||
onSelect: () => {
|
||||
var selectedValue = dropdownSelector.getValues(experimentFilter);
|
||||
if (selectedValue >= 0) {
|
||||
$(createTaskButton).removeAttr('disabled');
|
||||
} else {
|
||||
$(createTaskButton).attr('disabled', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(createTaskButton).click((e) => {
|
||||
var params = {};
|
||||
if (dropdownSelector.getValues(projectFilter) === '0') {
|
||||
params.project = {
|
||||
name: dropdownSelector.getData(projectFilter)[0].label,
|
||||
visibility: $('input[name="projects-visibility-selector"]:checked').val()
|
||||
};
|
||||
} else {
|
||||
params.project = { id: dropdownSelector.getValues(projectFilter) };
|
||||
}
|
||||
if (dropdownSelector.getValues(experimentFilter) === '0') {
|
||||
params.experiment = { name: dropdownSelector.getData(experimentFilter)[0].label };
|
||||
} else {
|
||||
params.experiment = { id: dropdownSelector.getValues(experimentFilter) };
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$('#create-task-modal .select-block').attr('data-error', '');
|
||||
$.post($(createTaskButton).data('ajaxUrl'), params, function(data) {
|
||||
window.location.href = data.my_module_path;
|
||||
}).error((response) => {
|
||||
var errorsObject = response.responseJSON.error_object;
|
||||
var errorsText = response.responseJSON.errors.name.join(' ');
|
||||
$(`#create-task-modal .select-block[data-error-object="${errorsObject}"]`).attr('data-error', errorsText);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
if ($('.quick-start-widget').length) {
|
||||
initNewTaskModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
$(document).on('turbolinks:load', function() {
|
||||
DasboardQuickStartWidget.init();
|
||||
});
|
|
@ -329,6 +329,8 @@ function initCreateNewModal() {
|
|||
});
|
||||
});
|
||||
|
||||
if ($('#protocols-index').data('new-protocol')) link.click();
|
||||
|
||||
submitBtn.on("click", function() {
|
||||
// Submit the form inside modal
|
||||
$(this).closest(".modal").find(".modal-body form").submit();
|
||||
|
|
|
@ -186,6 +186,8 @@
|
|||
initSelectPicker();
|
||||
initRedirectToNewReportPage();
|
||||
});
|
||||
|
||||
if ($('#content-reports-index').data('new-report')) $('#new-report-btn').click();
|
||||
}
|
||||
|
||||
initDatatable();
|
||||
|
|
|
@ -380,7 +380,7 @@ var dropdownSelector = (function() {
|
|||
}
|
||||
} else {
|
||||
// on Close we blur search field
|
||||
dropdownContainer.find('.search-field').blur();
|
||||
dropdownContainer.find('.search-field').blur().val('');
|
||||
|
||||
// onClose event
|
||||
if (config.onClose) {
|
||||
|
@ -681,7 +681,7 @@ var dropdownSelector = (function() {
|
|||
// If we have alteast one tag, we need to remove placeholder from search field
|
||||
if (selector.data('config').selectAppearance === 'simple') {
|
||||
let selectedLabel = container.find('.tag-label');
|
||||
container.find('.search-field').attr('placeholder',
|
||||
container.find('.search-field').prop('placeholder',
|
||||
selectedLabel.length && selectedLabel.text().trim() !== '' ? selectedLabel.text().trim() : selector.data('placeholder'));
|
||||
} else {
|
||||
searchFieldValue.attr('placeholder',
|
||||
|
@ -837,7 +837,21 @@ var dropdownSelector = (function() {
|
|||
setData: function(selector, data) {
|
||||
if ($(selector).length === 0) return false;
|
||||
|
||||
setData($(selector), []);
|
||||
setData($(selector), data);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
// Select value
|
||||
selectValue: function(selector, value) {
|
||||
var $selector;
|
||||
var option;
|
||||
|
||||
if ($(selector).length === 0) return false;
|
||||
|
||||
$selector = $(selector);
|
||||
option = $selector.find(`option[value="${value}"]`)[0];
|
||||
setData($selector, [convertOptionToJson(option)]);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
|
68
app/assets/javascripts/sitewide/infinite_scroll.js
Normal file
68
app/assets/javascripts/sitewide/infinite_scroll.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
|
||||
var InfiniteScroll = (function() {
|
||||
function getScrollHeight($container) {
|
||||
return $container[0].scrollHeight;
|
||||
}
|
||||
|
||||
function scrollNotVisible($container) {
|
||||
return (getScrollHeight($container) - $container.height() - 150 <= 0);
|
||||
}
|
||||
|
||||
function loadData($container, page = 1) {
|
||||
var customParams = $container.data('config').customParams;
|
||||
var params = (customParams ? customParams({ page: page }) : { page: page });
|
||||
|
||||
if ($container.hasClass('loading') || $container.hasClass('last-page')) return;
|
||||
$container.addClass('loading');
|
||||
|
||||
$.get($container.data('config').url, params, function(result) {
|
||||
if ($container.data('config').customResponse) {
|
||||
$container.data('config').customResponse(result, $container);
|
||||
} else {
|
||||
$(result.data).appendTo($container);
|
||||
}
|
||||
|
||||
if (result.next_page) {
|
||||
$container.data('next-page', result.next_page);
|
||||
} else {
|
||||
$container.addClass('last-page');
|
||||
}
|
||||
$container.removeClass('loading');
|
||||
|
||||
if (scrollNotVisible($container)) {
|
||||
loadData($container, $container.data('next-page'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initScroll(object, config = {}) {
|
||||
var $container = $(object);
|
||||
$container.data('next-page', 2);
|
||||
$container.data('config', config);
|
||||
|
||||
if (config.loadFirstPage) {
|
||||
loadData($container, 1);
|
||||
} else if (scrollNotVisible($container)) {
|
||||
loadData($container, $container.data('next-page'));
|
||||
}
|
||||
|
||||
$container.on('scroll', () => {
|
||||
if ($container.scrollTop() + $container.height() > getScrollHeight($container) - 150 && !$container.hasClass('last-page')) {
|
||||
loadData($container, $container.data('next-page'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init: (object, config) => {
|
||||
initScroll(object, config);
|
||||
},
|
||||
resetScroll: (object) => {
|
||||
$(object).data('next-page', 2).removeClass('last-page');
|
||||
if (scrollNotVisible($(object))) {
|
||||
loadData($(object), $(object).data('next-page'));
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
|
@ -31,6 +31,7 @@
|
|||
@import "my_modules/results/*";
|
||||
@import "my_modules/*";
|
||||
@import "protocols/*";
|
||||
@import "dashboard/*";
|
||||
@import "repository/*";
|
||||
@import "repository_columns/*";
|
||||
@import "settings/*";
|
||||
|
|
141
app/assets/stylesheets/dashboard/calendar.scss
Normal file
141
app/assets/stylesheets/dashboard/calendar.scss
Normal file
|
@ -0,0 +1,141 @@
|
|||
// scss-lint:disable SelectorDepth
|
||||
// scss-lint:disable NestingDepth
|
||||
|
||||
.dashboard-container .calendar-widget {
|
||||
grid-column: 10 / span 3;
|
||||
grid-row: 1 / span 6;
|
||||
|
||||
.dashboard-calendar {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.clndr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.controls {
|
||||
border-bottom: $border-default;
|
||||
display: flex;
|
||||
flex-basis: 42px;
|
||||
padding: 3px;
|
||||
|
||||
.clndr-title {
|
||||
@include font-h3;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.days-container {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
flex-grow: 1;
|
||||
grid-column-gap: 6px;
|
||||
grid-row-gap: 6px;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-template-rows: repeat(7, 1fr);
|
||||
justify-items: center;
|
||||
padding: 6px;
|
||||
|
||||
.day-header {
|
||||
@include font-button;
|
||||
color: $color-silver-chalice;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.day {
|
||||
@include font-button;
|
||||
align-items: center;
|
||||
animation-timing-function: $timing-function-sharp;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
transition: .3s;
|
||||
user-select: none;
|
||||
width: 32px;
|
||||
|
||||
&.adjacent-month {
|
||||
color: $color-alto;
|
||||
}
|
||||
|
||||
&.event {
|
||||
|
||||
.event-day {
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 30px;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
|
||||
&:hover {
|
||||
background: $color-concrete;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&::after {
|
||||
background: $brand-danger;
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
height: 4px;
|
||||
left: 14px;
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
width: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&.today {
|
||||
border: $border-primary;
|
||||
|
||||
&.event {
|
||||
&::after {
|
||||
left: 13px;
|
||||
top: 23px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.events-container {
|
||||
color: $color-black;
|
||||
padding: 8px;
|
||||
width: 280px;
|
||||
|
||||
.title {
|
||||
@include font-h3;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1250px) {
|
||||
.dashboard-container .calendar-widget {
|
||||
grid-column: 9 / span 4;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.dashboard-container .calendar-widget {
|
||||
grid-column: 1 / span 6;
|
||||
grid-row: 5 / span 4;
|
||||
|
||||
.clndr {
|
||||
.events-container {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
92
app/assets/stylesheets/dashboard/create_task_modal.scss
Normal file
92
app/assets/stylesheets/dashboard/create_task_modal.scss
Normal file
|
@ -0,0 +1,92 @@
|
|||
// scss-lint:disable SelectorDepth QualifyingElement NestingDepth
|
||||
|
||||
#create-task-modal {
|
||||
.modal-dialog {
|
||||
width: 360px;
|
||||
|
||||
.description {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.select-block {
|
||||
display: inline-block;
|
||||
padding-bottom: 16px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
@include font-small;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
color: $brand-danger;
|
||||
content: attr(data-error);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-selector-container {
|
||||
.create-new {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.new-projects-visibility {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.down-arrow {
|
||||
background-color: $color-white;
|
||||
box-shadow: -4px 4px 0 $color-black;
|
||||
display: inline-block;
|
||||
height: 38px;
|
||||
margin: 0 11px 20px 14px;
|
||||
opacity: .2;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.down-arrow::before {
|
||||
border-bottom: 6px solid transparent;
|
||||
border-left: 8px solid $color-black;
|
||||
border-top: 6px solid transparent;
|
||||
content: "";
|
||||
height: 0;
|
||||
left: 46px;
|
||||
position: absolute;
|
||||
top: 36px;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.project-visibility-container {
|
||||
display: inline-block;
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
.project-visibility-title {
|
||||
@include font-small;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.sci-toggles-group {
|
||||
.sci-toggle-item {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.sci-toggle-item-label {
|
||||
margin-left: -130px;
|
||||
margin-bottom: 0;
|
||||
width: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
335
app/assets/stylesheets/dashboard/current_tasks.scss
Normal file
335
app/assets/stylesheets/dashboard/current_tasks.scss
Normal file
|
@ -0,0 +1,335 @@
|
|||
// scss-lint:disable SelectorDepth
|
||||
// scss-lint:disable NestingDepth
|
||||
|
||||
.dashboard-container .current-tasks-widget {
|
||||
grid-column: 1 / span 9;
|
||||
grid-row: 1 / span 6;
|
||||
|
||||
.title {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
flex-basis: 36px;
|
||||
|
||||
.fa-search {
|
||||
animation-timing-function: $timing-function-sharp;
|
||||
color: $color-alto;
|
||||
transition: .3s;
|
||||
width: 26px;
|
||||
}
|
||||
|
||||
.task-search-field {
|
||||
background: transparent;
|
||||
border: $border-default;
|
||||
padding-left: 36px;
|
||||
position: relative;
|
||||
width: 200px;
|
||||
z-index: 2;
|
||||
|
||||
&:placeholder-shown {
|
||||
border: $border-transparent;
|
||||
cursor: pointer;
|
||||
width: 36px;
|
||||
|
||||
+ .fa-search {
|
||||
color: $color-volcano;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: $border-default;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: $border-focus;
|
||||
cursor: auto;
|
||||
width: 200px;
|
||||
|
||||
+ .fa-search {
|
||||
color: $color-alto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
height: 36px;
|
||||
margin-right: 4px;
|
||||
width: 36px;
|
||||
|
||||
.curent-tasks-filters {
|
||||
padding: 0;
|
||||
width: 230px;
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
border-bottom: $border-default;
|
||||
display: flex;
|
||||
height: 44px;
|
||||
margin-bottom: 16px;
|
||||
padding: 0 5px 0 16px;
|
||||
|
||||
.title {
|
||||
@include font-h2;
|
||||
flex-grow: 1;
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.select-block {
|
||||
display: inline-block;
|
||||
padding: 0 16px 16px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
@include font-small;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
align-items: center;
|
||||
border-top: $border-default;
|
||||
display: flex;
|
||||
height: 68px;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-tasks {
|
||||
color: $color-alto;
|
||||
margin-left: 8px;
|
||||
margin-top: 16px;
|
||||
|
||||
.text-1 {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-2 {
|
||||
color: $color-silver-chalice;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.fas {
|
||||
font-size: 32px;
|
||||
margin-left: 100px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.current-tasks-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 0 16px;
|
||||
position: relative;
|
||||
|
||||
.current-task-item {
|
||||
border-bottom: $border-tertiary;
|
||||
color: $color-volcano;
|
||||
padding: 6px;
|
||||
text-decoration: none;
|
||||
|
||||
.current-task-breadcrumbs {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
line-height: 14px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
.slash {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.item-row {
|
||||
display: flex;
|
||||
|
||||
.task-name {
|
||||
flex-grow: 1;
|
||||
font-size: $font-size-base;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
padding-right: 10px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-due-date {
|
||||
flex-basis: 280px;
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
|
||||
.fas {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
&.overdue {
|
||||
color: $brand-danger;
|
||||
}
|
||||
|
||||
&.day-prior {
|
||||
color: $brand-warning;
|
||||
}
|
||||
|
||||
&.completed {
|
||||
color: $brand-success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $color-concrete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-progress-container {
|
||||
height: 20px;
|
||||
max-width: 250px;
|
||||
min-width: 150px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&::after {
|
||||
@include font-small;
|
||||
@include font-awesome;
|
||||
content: "";
|
||||
line-height: 18px;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.task-progress {
|
||||
background: $brand-focus-light;
|
||||
border: $border-tertiary;
|
||||
border-radius: $border-radius-tag;
|
||||
display: flex;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
background: $color-white;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.task-progress-label {
|
||||
@include font-small;
|
||||
font-weight: bold;
|
||||
height: 20px;
|
||||
left: 0;
|
||||
line-height: 20px;
|
||||
padding-left: 8px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
&.overdue {
|
||||
.task-progress {
|
||||
background: $brand-danger-light;
|
||||
}
|
||||
|
||||
.task-progress-label {
|
||||
color: $brand-danger;
|
||||
}
|
||||
|
||||
&::after {
|
||||
color: $brand-danger;
|
||||
content: $font-fas-exclamation-triangle;
|
||||
}
|
||||
}
|
||||
|
||||
&.day-prior {
|
||||
.task-progress-label {
|
||||
color: $brand-warning;
|
||||
}
|
||||
}
|
||||
|
||||
&.completed {
|
||||
.task-progress {
|
||||
outline: $border-success;
|
||||
}
|
||||
|
||||
.task-progress,
|
||||
.task-progress::after {
|
||||
background: $brand-success-light;
|
||||
}
|
||||
|
||||
.task-progress-label {
|
||||
color: $brand-success;
|
||||
}
|
||||
|
||||
&::after {
|
||||
color: $brand-success;
|
||||
content: $font-fas-check;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1500px) {
|
||||
.dashboard-container .current-tasks-widget {
|
||||
.task-progress-container {
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1250px) {
|
||||
.dashboard-container .current-tasks-widget {
|
||||
grid-column: 1 / span 8;
|
||||
|
||||
.task-progress-container {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.current-tasks-list {
|
||||
.current-task-item {
|
||||
.item-row {
|
||||
.task-due-date {
|
||||
flex-basis: 230px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.dashboard-container .current-tasks-widget {
|
||||
grid-column: 1 / span 12;
|
||||
grid-row: 1 / span 4;
|
||||
|
||||
.no-tasks .fas {
|
||||
margin-left: 500px;
|
||||
}
|
||||
}
|
||||
}
|
37
app/assets/stylesheets/dashboard/quick_start.scss
Normal file
37
app/assets/stylesheets/dashboard/quick_start.scss
Normal file
|
@ -0,0 +1,37 @@
|
|||
.dashboard-container .quick-start-widget {
|
||||
grid-column: 1 / span 2;
|
||||
grid-row: 7 / span 6;
|
||||
|
||||
.widget-body {
|
||||
padding: 16px;
|
||||
|
||||
.quick-start-description {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
margin-bottom: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1700px) {
|
||||
.dashboard-container .quick-start-widget {
|
||||
grid-column: 1 / span 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 1250px) {
|
||||
.dashboard-container .quick-start-widget {
|
||||
grid-column: 1 / span 4;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.dashboard-container .quick-start-widget {
|
||||
grid-column: 7 / span 6;
|
||||
grid-row: 5 / span 4;
|
||||
}
|
||||
}
|
24
app/assets/stylesheets/dashboard/recent_work.scss
Normal file
24
app/assets/stylesheets/dashboard/recent_work.scss
Normal file
|
@ -0,0 +1,24 @@
|
|||
.dashboard-container .recent-work-widget {
|
||||
grid-column: 3 / span 7;
|
||||
grid-row: 7 / span 6;
|
||||
}
|
||||
|
||||
@media (max-width: 1700px) {
|
||||
.dashboard-container .recent-work-widget {
|
||||
grid-column: 4 / span 6;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 1250px) {
|
||||
.dashboard-container .recent-work-widget {
|
||||
grid-column: 5 / span 8;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.dashboard-container .recent-work-widget {
|
||||
grid-column: 1 / span 12;
|
||||
grid-row: 9 / span 4;
|
||||
}
|
||||
}
|
43
app/assets/stylesheets/dashboard/show.scss
Normal file
43
app/assets/stylesheets/dashboard/show.scss
Normal file
|
@ -0,0 +1,43 @@
|
|||
.dashboard-container {
|
||||
--widget-header-size: 44px;
|
||||
--dashboard-widgets-gap: 30px;
|
||||
display: grid;
|
||||
grid-column-gap: var(--dashboard-widgets-gap);
|
||||
grid-row-gap: var(--dashboard-widgets-gap);
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
grid-template-rows: repeat(12, 1fr);
|
||||
min-height: calc(100vh - 51px);
|
||||
padding: var(--dashboard-widgets-gap) calc(var(--dashboard-widgets-gap) - 15px);
|
||||
width: 100%;
|
||||
|
||||
.basic-widget {
|
||||
border-radius: $border-radius-modal;
|
||||
box-shadow: $flyout-shadow;
|
||||
position: relative;
|
||||
|
||||
.widget-header {
|
||||
align-items: center;
|
||||
border-bottom: $border-tertiary;
|
||||
display: flex;
|
||||
|
||||
height: var(--widget-header-size);
|
||||
padding-left: 16px;
|
||||
|
||||
.widget-title {
|
||||
@include font-h2;
|
||||
}
|
||||
}
|
||||
|
||||
.widget-body {
|
||||
height: calc(100% - var(--widget-header-size));
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1300px) {
|
||||
.dashboard-container {
|
||||
--dashboard-widgets-gap: 16px;
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@
|
|||
}
|
||||
|
||||
.search-field {
|
||||
@include font-button;
|
||||
border: 0;
|
||||
flex-basis: 0;
|
||||
flex-grow: 2000;
|
||||
|
@ -95,6 +96,10 @@
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
|
||||
&[data-ds-tag-id=""] {
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
|
||||
.fas {
|
||||
|
@ -108,6 +113,7 @@
|
|||
}
|
||||
|
||||
.dropdown-container {
|
||||
@include font-button;
|
||||
background: $color-white;
|
||||
border: 1px solid $color-alto;
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
@ -172,7 +178,6 @@
|
|||
color: $color-white;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.checkbox-icon {
|
||||
|
@ -264,6 +269,7 @@
|
|||
|
||||
&[data-options-selected="0"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
59
app/assets/stylesheets/shared/my_modules_list_partial.scss
Normal file
59
app/assets/stylesheets/shared/my_modules_list_partial.scss
Normal file
|
@ -0,0 +1,59 @@
|
|||
// scss-lint:disable SelectorDepth
|
||||
// scss-lint:disable NestingDepth
|
||||
|
||||
.my-modules-list-partial {
|
||||
width: 100%;
|
||||
|
||||
.task-group:not(:first-child) {
|
||||
border-top: $border-tertiary;
|
||||
}
|
||||
|
||||
.header {
|
||||
@include font-small;
|
||||
align-items: center;
|
||||
color: $color-silver-chalice;
|
||||
display: flex;
|
||||
height: 20px;
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
|
||||
.project,
|
||||
.experiment {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.slash {
|
||||
flex-basis: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tasks {
|
||||
@include font-button;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.task {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
line-height: 25px;
|
||||
|
||||
.task-icon {
|
||||
flex-shrink: 0;
|
||||
margin-right: 9px;
|
||||
|
||||
path {
|
||||
fill: $brand-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.task-link {
|
||||
line-height: 24px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@ $border-default: 1px solid $color-alto;
|
|||
$border-secondary: 1px solid $color-silver-chalice;
|
||||
$border-tertiary: 1px solid $color-concrete;
|
||||
|
||||
$border-primary: 1px solid $brand-primary;
|
||||
$border-focus: 1px solid $brand-focus;
|
||||
$border-success: 1px solid $brand-success;
|
||||
$border-danger: 1px solid $brand-danger;
|
||||
$border-transparent: 1px solid transparent;
|
||||
|
|
|
@ -30,6 +30,10 @@
|
|||
&:disabled {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: $color-alto;
|
||||
}
|
||||
}
|
||||
|
||||
.fas {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
.sci-secondary-navbar {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
.navbar-link {
|
||||
@include font-small;
|
||||
align-items: center;
|
||||
color: $color-silver-chalice;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 0 16px;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:hover {
|
||||
color: $color-volcano;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $color-volcano;
|
||||
font-weight: bold;
|
||||
|
||||
&::before {
|
||||
background: $brand-primary;
|
||||
bottom: 0;
|
||||
content: "";
|
||||
height: 4px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
app/controllers/dashboard/calendars_controller.rb
Normal file
35
app/controllers/dashboard/calendars_controller.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
class CalendarsController < ApplicationController
|
||||
include IconsHelper
|
||||
|
||||
def show
|
||||
date = DateTime.parse(params[:date])
|
||||
start_date = date.at_beginning_of_month.utc - 7.days
|
||||
end_date = date.at_end_of_month.utc + 14.days
|
||||
due_dates = current_user.my_modules.active.uncomplete
|
||||
.joins(experiment: :project)
|
||||
.where(experiments: { archived: false })
|
||||
.where(projects: { archived: false })
|
||||
.where('my_modules.due_date > ? AND my_modules.due_date < ?', start_date, end_date)
|
||||
.joins(:protocols).where(protocols: { team_id: current_team.id })
|
||||
.pluck(:due_date)
|
||||
render json: { events: due_dates.map { |i| { date: i } } }
|
||||
end
|
||||
|
||||
def day
|
||||
date = DateTime.parse(params[:date]).utc
|
||||
my_modules = current_user.my_modules.active.uncomplete
|
||||
.joins(experiment: :project)
|
||||
.where(experiments: { archived: false })
|
||||
.where(projects: { archived: false })
|
||||
.where('DATE(my_modules.due_date) = DATE(?)', date)
|
||||
.where(projects: { team_id: current_team.id })
|
||||
.my_modules_list_partial
|
||||
render json: {
|
||||
html: render_to_string(partial: 'shared/my_modules_list_partial.html.erb', locals: { task_groups: my_modules })
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
133
app/controllers/dashboard/current_tasks_controller.rb
Normal file
133
app/controllers/dashboard/current_tasks_controller.rb
Normal file
|
@ -0,0 +1,133 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
class CurrentTasksController < ApplicationController
|
||||
include InputSanitizeHelper
|
||||
|
||||
before_action :load_project, only: %i(show experiment_filter)
|
||||
before_action :load_experiment, only: :show
|
||||
before_action :check_task_view_permissions, only: :show
|
||||
|
||||
def show
|
||||
tasks = if @experiment
|
||||
@experiment.my_modules.active
|
||||
elsif @project
|
||||
MyModule.active.where(projects: { id: @project.id })
|
||||
else
|
||||
MyModule.active.viewable_by_user(current_user, current_team)
|
||||
end
|
||||
|
||||
tasks = tasks.joins(experiment: :project)
|
||||
.where(experiments: { archived: false })
|
||||
.where(projects: { archived: false })
|
||||
|
||||
if task_filters[:mode] == 'assigned'
|
||||
tasks = tasks.left_outer_joins(:user_my_modules).where(user_my_modules: { user_id: current_user.id })
|
||||
end
|
||||
|
||||
tasks = filter_by_state(tasks)
|
||||
|
||||
case task_filters[:sort]
|
||||
when 'date_desc'
|
||||
tasks = tasks.order('my_modules.due_date': :desc).order('my_modules.name': :asc)
|
||||
when 'date_asc'
|
||||
tasks = tasks.order('my_modules.due_date': :asc).order('my_modules.name': :asc)
|
||||
when 'atoz'
|
||||
tasks = tasks.order('my_modules.name': :asc)
|
||||
when 'ztoa'
|
||||
tasks = tasks.order('my_modules.name': :desc)
|
||||
else
|
||||
tasks
|
||||
end
|
||||
|
||||
page = (params[:page] || 1).to_i
|
||||
tasks = tasks.with_step_statistics.search_by_name(current_user, current_team, task_filters[:query])
|
||||
.preload(experiment: :project).page(page).per(Constants::INFINITE_SCROLL_LIMIT)
|
||||
|
||||
tasks_list = tasks.map do |task|
|
||||
{ id: task.id,
|
||||
link: protocols_my_module_path(task.id),
|
||||
experiment: escape_input(task.experiment.name),
|
||||
project: escape_input(task.experiment.project.name),
|
||||
name: escape_input(task.name),
|
||||
due_date: task.due_date.present? ? I18n.l(task.due_date, format: :full_date) : nil,
|
||||
state: task_state(task),
|
||||
steps_precentage: task.steps_completed_percentage }
|
||||
end
|
||||
|
||||
render json: { data: tasks_list, next_page: tasks.next_page }
|
||||
end
|
||||
|
||||
def project_filter
|
||||
projects = current_team.projects
|
||||
.where(archived: false)
|
||||
.viewable_by_user(current_user, current_team)
|
||||
.search_by_name(current_user, current_team, params[:query]).select(:id, :name)
|
||||
|
||||
unless params[:mode] == 'team'
|
||||
projects = projects.where(id: current_user.my_modules.joins(:experiment)
|
||||
.group(:project_id).select(:project_id).pluck(:project_id))
|
||||
end
|
||||
render json: projects.map { |i| { value: i.id, label: escape_input(i.name) } }, status: :ok
|
||||
end
|
||||
|
||||
def experiment_filter
|
||||
unless @project
|
||||
render json: []
|
||||
return false
|
||||
end
|
||||
experiments = @project.experiments
|
||||
.where(archived: false)
|
||||
.viewable_by_user(current_user, current_team)
|
||||
.search_by_name(current_user, current_team, params[:query]).select(:id, :name)
|
||||
|
||||
unless params[:mode] == 'team'
|
||||
experiments = experiments.where(id: current_user.my_modules
|
||||
.group(:experiment_id).select(:experiment_id).pluck(:experiment_id))
|
||||
end
|
||||
render json: experiments.map { |i| { value: i.id, label: escape_input(i.name) } }, status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def task_state(task)
|
||||
if task.state == 'completed'
|
||||
task_state_class = task.state
|
||||
task_state_text = t('dashboard.current_tasks.progress_bar.completed')
|
||||
else
|
||||
task_state_text = t('dashboard.current_tasks.progress_bar.in_progress')
|
||||
task_state_class = 'day-prior' if task.is_one_day_prior?
|
||||
if task.is_overdue?
|
||||
task_state_text = t('dashboard.current_tasks.progress_bar.overdue')
|
||||
task_state_class = 'overdue'
|
||||
end
|
||||
if task.steps_total.positive?
|
||||
task_state_text += t('dashboard.current_tasks.progress_bar.completed_steps',
|
||||
steps: task.steps_completed, total_steps: task.steps_total)
|
||||
end
|
||||
end
|
||||
{ text: task_state_text, class: task_state_class }
|
||||
end
|
||||
|
||||
def filter_by_state(tasks)
|
||||
tasks.where(my_modules: { state: task_filters[:view] })
|
||||
end
|
||||
|
||||
def task_filters
|
||||
params.permit(:project_id, :experiment_id, :mode, :view, :sort, :query, :page)
|
||||
end
|
||||
|
||||
def load_project
|
||||
@project = current_team.projects.find_by(id: params[:project_id])
|
||||
end
|
||||
|
||||
def load_experiment
|
||||
@experiment = @project.experiments.find_by(id: params[:experiment_id]) if @project
|
||||
end
|
||||
|
||||
def check_task_view_permissions
|
||||
render_403 if @project && !can_read_project?(@project)
|
||||
render_403 if @experiment && !can_read_experiment?(@experiment)
|
||||
end
|
||||
end
|
||||
end
|
80
app/controllers/dashboard/quick_start_controller.rb
Normal file
80
app/controllers/dashboard/quick_start_controller.rb
Normal file
|
@ -0,0 +1,80 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
class QuickStartController < ApplicationController
|
||||
include InputSanitizeHelper
|
||||
|
||||
before_action :load_project, only: %i(create_task experiment_filter)
|
||||
before_action :load_experiment, only: :create_task
|
||||
before_action :check_task_create_permissions, only: :create_task
|
||||
|
||||
def create_task
|
||||
my_module = CreateMyModuleService.new(current_user, current_team,
|
||||
project: @project || create_project_params,
|
||||
experiment: @experiment || create_experiment_params).call
|
||||
if my_module.errors.empty?
|
||||
render json: { my_module_path: protocols_my_module_path(my_module) }
|
||||
else
|
||||
render json: { errors: my_module.errors, error_object: my_module.class.name }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def project_filter
|
||||
projects = current_team.projects.search(current_user, false, params[:query], 1, current_team)
|
||||
.where('user_projects.role <= 1')
|
||||
.select(:id, :name)
|
||||
projects = projects.map { |i| { value: i.id, label: escape_input(i.name) } }
|
||||
if (projects.map { |i| i[:label] }.exclude? params[:query]) && params[:query].present?
|
||||
projects = [{ value: 0, label: params[:query] }] + projects
|
||||
end
|
||||
render json: projects, status: :ok
|
||||
end
|
||||
|
||||
def experiment_filter
|
||||
if create_project_params.present? && params[:query].present?
|
||||
experiments = [{ value: 0, label: params[:query] }]
|
||||
elsif @project
|
||||
experiments = @project.experiments
|
||||
.search(current_user, false, params[:query], 1, current_team)
|
||||
.select(:id, :name)
|
||||
experiments = experiments.map { |i| { value: i.id, label: escape_input(i.name) } }
|
||||
if (experiments.map { |i| i[:label] }.exclude? params[:query]) && params[:query].present?
|
||||
experiments = [{ value: 0, label: params[:query] }] + experiments
|
||||
end
|
||||
end
|
||||
render json: experiments || [], status: :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_project_params
|
||||
params.require(:project).permit(:name, :visibility)
|
||||
end
|
||||
|
||||
def create_experiment_params
|
||||
params.require(:experiment).permit(:name)
|
||||
end
|
||||
|
||||
def load_project
|
||||
@project = current_team.projects.find_by(id: params.dig(:project, :id))
|
||||
end
|
||||
|
||||
def load_experiment
|
||||
@experiment = @project.experiments.find_by(id: params.dig(:experiment, :id)) if @project
|
||||
end
|
||||
|
||||
def check_task_create_permissions
|
||||
unless @project
|
||||
render_403 unless can_create_projects?(current_user, current_team)
|
||||
return
|
||||
end
|
||||
|
||||
unless @experiment
|
||||
render_403 unless can_create_experiments?(current_user, @project)
|
||||
return
|
||||
end
|
||||
|
||||
render_403 unless can_manage_experiment?(current_user, @experiment)
|
||||
end
|
||||
end
|
||||
end
|
5
app/controllers/dashboards_controller.rb
Normal file
5
app/controllers/dashboards_controller.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DashboardsController < ApplicationController
|
||||
def show; end
|
||||
end
|
|
@ -95,7 +95,7 @@ class ExperimentsController < ApplicationController
|
|||
@experiment.last_modified_by = current_user
|
||||
|
||||
if @experiment.save
|
||||
experiment_annotation_notification(old_text)
|
||||
experiment_annotation_notification(old_text) if old_text
|
||||
|
||||
activity_type = if experiment_params[:archived] == 'false'
|
||||
:restore_experiment
|
||||
|
|
|
@ -13,6 +13,8 @@ module IconsHelper
|
|||
when 'shared-read'
|
||||
title = "<title>#{t('repositories.icon_title.shared_read', team_name: team.name)}</title>"
|
||||
icon = '<path fill="#A0A0A0" fill-rule="evenodd" d="M6.6 8.922c-.688 0-1.34-.172-1.925-.515a3.478 3.478 0 0 1-1.41-1.41 3.758 3.758 0 0 1-.515-1.925c0-.687.172-1.306.516-1.925.343-.584.79-1.065 1.409-1.41A3.758 3.758 0 0 1 6.6 1.223a3.85 3.85 0 0 1 1.925.516 3.955 3.955 0 0 1 1.41 1.41 3.85 3.85 0 0 1 .515 1.924c0 .688-.172 1.34-.516 1.925-.343.619-.825 1.066-1.409 1.41a3.85 3.85 0 0 1-1.925.515zm2.647 1.1c.687 0 1.34.207 1.96.55.618.344 1.1.825 1.443 1.444.281.506.47 1.036.53 1.588a7.217 7.217 0 0 0-2.564 2.568 2.64 2.64 0 0 0-.218.45H1.65c-.481 0-.86-.137-1.169-.481-.343-.31-.481-.687-.481-1.169v-.997c0-.687.172-1.34.516-1.959.343-.619.825-1.1 1.443-1.444.62-.343 1.272-.55 1.994-.55h.275c.756.378 1.547.55 2.372.55a5.43 5.43 0 0 0 2.372-.55h.275zm11.43 3.546c.442.263.85.562 1.22.898.068-.18.103-.376.103-.594a3.85 3.85 0 0 0-.516-1.925 3.955 3.955 0 0 0-1.409-1.41 3.849 3.849 0 0 0-1.925-.515h-.137a4.156 4.156 0 0 1-1.513.275c-.481 0-.997-.069-1.512-.275h-.138c-.688 0-1.306.172-1.925.516.412.481.756 1.031.997 1.615.125.306.223.62.288.94a7.268 7.268 0 0 1 2.748-.527c1.326 0 2.577.34 3.704.994l.014.008zM16.5 8.922c-.928 0-1.719-.31-2.338-.962-.653-.619-.962-1.41-.962-2.338 0-.894.31-1.684.963-2.337a3.216 3.216 0 0 1 2.337-.963c.894 0 1.684.344 2.337.963.62.653.963 1.443.963 2.337 0 .928-.344 1.719-.963 2.338a3.193 3.193 0 0 1-2.337.962zm5.347 8.6a.932.932 0 0 0-.119-.407 5.658 5.658 0 0 0-1.986-1.969 5.473 5.473 0 0 0-2.784-.747 5.428 5.428 0 0 0-2.784.747 5.39 5.39 0 0 0-1.986 1.97.75.75 0 0 0-.119.407c0 .152.034.288.12.407a5.148 5.148 0 0 0 1.985 1.97c.85.509 1.766.746 2.784.746 1.002 0 1.936-.237 2.784-.747a5.39 5.39 0 0 0 1.986-1.969.818.818 0 0 0 .12-.407zm-3.734 2.004a2.303 2.303 0 0 1-1.155.305c-.424 0-.814-.101-1.154-.305a2.264 2.264 0 0 1-.849-.849 2.214 2.214 0 0 1-.305-1.154c0-.408.101-.798.305-1.155.204-.34.492-.628.849-.831.34-.204.73-.323 1.154-.323.408 0 .798.119 1.155.323.34.203.628.492.831.831.204.357.323.747.323 1.155 0 .424-.119.815-.323 1.154a2.346 2.346 0 0 1-.831.849zm.085-3.242c.339.34.526.764.526 1.239 0 .492-.187.916-.526 1.256-.34.34-.764.51-1.24.51-.492 0-.916-.17-1.256-.51-.34-.34-.51-.764-.51-1.256 0-.289.069-.56.205-.832 0 .204.067.39.203.526a.73.73 0 0 0 .527.204.691.691 0 0 0 .509-.204.745.745 0 0 0 .22-.526.706.706 0 0 0-.22-.51.706.706 0 0 0-.51-.22c.255-.136.527-.204.832-.204.476 0 .9.187 1.24.527z" clip-rule="evenodd"/>'
|
||||
when 'task-icon'
|
||||
return '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" class="task-icon"><path d="M6.646 14V7.215H4V5h7.938v2.215H9.292V14h4.144c.26 0 .434-.009.542-.022.013-.107.022-.282.022-.542V2.564c0-.26-.009-.435-.022-.542A4.762 4.762 0 0 0 13.436 2H2.564c-.26 0-.435.009-.542.022A4.762 4.762 0 0 0 2 2.564v10.872c0 .26.009.434.022.542.107.013.282.022.542.022h4.082zM2.564 0h10.872c.892 0 1.215.093 1.54.267.327.174.583.43.757.756.174.326.267.65.267 1.54v10.873c0 .892-.093 1.215-.267 1.54-.174.327-.43.583-.756.757-.326.174-.65.267-1.54.267H2.563c-.892 0-1.215-.093-1.54-.267a1.817 1.817 0 0 1-.757-.756C.093 14.65 0 14.327 0 13.437V2.563c0-.892.093-1.215.267-1.54.174-.327.43-.583.756-.757C1.35.093 1.673 0 2.563 0z" fill="#B3B3B3" fill-rule="evenodd"/></svg>'.html_safe
|
||||
end
|
||||
('<svg class="fas-custom" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22">' + title + icon + '</svg>').html_safe
|
||||
end
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
module LeftMenuBarHelper
|
||||
|
||||
def dashboard_are_selected?
|
||||
controller_name == 'dashboards'
|
||||
end
|
||||
|
||||
def projects_are_selected?
|
||||
controller_name.in? %w(projects experiments my_modules)
|
||||
end
|
||||
|
|
|
@ -80,6 +80,16 @@ class MyModule < ApplicationRecord
|
|||
end)
|
||||
scope :workflow_ordered, -> { order(workflow_order: :asc) }
|
||||
scope :uncomplete, -> { where(state: 'uncompleted') }
|
||||
scope :with_step_statistics, (lambda do
|
||||
left_outer_joins(protocols: :steps)
|
||||
.group(:id)
|
||||
.select('my_modules.*')
|
||||
.select('COUNT(steps.id) AS steps_total')
|
||||
.select('COUNT(steps.id) FILTER (where steps.completed = true) AS steps_completed')
|
||||
.select('CASE COUNT(steps.id) WHEN 0 THEN 0 ELSE'\
|
||||
'((COUNT(steps.id) FILTER (where steps.completed = true)) * 100 / COUNT(steps.id)) '\
|
||||
'END AS steps_completed_percentage')
|
||||
end)
|
||||
|
||||
# A module takes this much space in canvas (x, y) in database
|
||||
WIDTH = 30
|
||||
|
@ -514,6 +524,21 @@ class MyModule < ApplicationRecord
|
|||
self.completed_on = nil
|
||||
end
|
||||
|
||||
def self.my_modules_list_partial
|
||||
ungrouped_tasks = joins(experiment: :project)
|
||||
.select('experiments.name as experiment_name,
|
||||
projects.name as project_name,
|
||||
my_modules.name as task_name,
|
||||
my_modules.id')
|
||||
ungrouped_tasks.group_by { |i| [i[:project_name], i[:experiment_name]] }.map do |group, tasks|
|
||||
{
|
||||
project_name: group[0],
|
||||
experiment_name: group[1],
|
||||
tasks: tasks.map { |task| { id: task.id, task_name: task.task_name } }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_blank_protocol
|
||||
|
|
44
app/services/create_experiment_service.rb
Normal file
44
app/services/create_experiment_service.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateExperimentService
|
||||
def initialize(user, team, params)
|
||||
@params = params
|
||||
@user = user
|
||||
@team = team
|
||||
end
|
||||
|
||||
def call
|
||||
new_experiment = nil
|
||||
ActiveRecord::Base.transaction do
|
||||
unless @params[:project].class == Project
|
||||
@params[:project] = CreateProjectService.new(@user, @team, @params[:project]).call
|
||||
end
|
||||
unless @params[:project]&.errors&.empty?
|
||||
new_experiment = @params[:project]
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
@params[:created_by] = @user
|
||||
@params[:last_modified_by] = @user
|
||||
|
||||
@experiment = @params[:project].experiments.new(@params)
|
||||
|
||||
create_experiment_activity if @experiment.save
|
||||
|
||||
new_experiment = @experiment
|
||||
end
|
||||
new_experiment
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_experiment_activity
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: :create_experiment,
|
||||
owner: @user,
|
||||
subject: @experiment,
|
||||
team: @team,
|
||||
project: @experiment.project,
|
||||
message_items: { experiment: @experiment.id })
|
||||
end
|
||||
end
|
52
app/services/create_my_module_service.rb
Normal file
52
app/services/create_my_module_service.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateMyModuleService
|
||||
def initialize(user, team, params)
|
||||
@params = params
|
||||
@my_module_params = params[:my_module] || {}
|
||||
@user = user
|
||||
@team = team
|
||||
end
|
||||
|
||||
def call
|
||||
new_my_module = nil
|
||||
ActiveRecord::Base.transaction do
|
||||
unless @params[:experiment].class == Experiment
|
||||
@params[:experiment][:project] = @params[:project]
|
||||
@params[:experiment] = CreateExperimentService.new(@user, @team, @params[:experiment]).call
|
||||
end
|
||||
unless @params[:experiment]&.errors&.empty?
|
||||
new_my_module = @params[:experiment]
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
@my_module_params[:x] ||= 0
|
||||
@my_module_params[:y] ||= 0
|
||||
@my_module_params[:name] ||= I18n.t('create_task_service.default_task_name')
|
||||
|
||||
@my_module = @params[:experiment].my_modules.new(@my_module_params)
|
||||
|
||||
new_pos = @my_module.get_new_position
|
||||
@my_module.x = new_pos[:x]
|
||||
@my_module.y = new_pos[:y]
|
||||
|
||||
@my_module.save!
|
||||
create_my_module_activity
|
||||
@params[:experiment].generate_workflow_img
|
||||
new_my_module = @my_module
|
||||
end
|
||||
new_my_module
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_my_module_activity
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: :create_module,
|
||||
owner: @user,
|
||||
team: @team,
|
||||
project: @params[:experiment].project,
|
||||
subject: @my_module,
|
||||
message_items: { my_module: @my_module.id })
|
||||
end
|
||||
end
|
42
app/services/create_project_service.rb
Normal file
42
app/services/create_project_service.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateProjectService
|
||||
def initialize(user, team, params)
|
||||
@params = params
|
||||
@user = user
|
||||
@team = team
|
||||
end
|
||||
|
||||
def call
|
||||
new_project = nil
|
||||
ActiveRecord::Base.transaction do
|
||||
@params[:created_by] = @user
|
||||
@params[:last_modified_by] = @user
|
||||
|
||||
@project = @team.projects.new(@params)
|
||||
|
||||
if @project.save
|
||||
@project.user_projects.create!(role: :owner, user: @user)
|
||||
create_project_activity
|
||||
new_project = @project
|
||||
else
|
||||
new_project = @project
|
||||
raise ActiveRecord::Rollback
|
||||
|
||||
end
|
||||
end
|
||||
new_project
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_project_activity
|
||||
Activities::CreateActivityService
|
||||
.call(activity_type: :create_project,
|
||||
owner: @user,
|
||||
subject: @project,
|
||||
team: @team,
|
||||
project: @project,
|
||||
message_items: { project: @project.id })
|
||||
end
|
||||
end
|
21
app/views/dashboards/_calendar.html.erb
Normal file
21
app/views/dashboards/_calendar.html.erb
Normal file
|
@ -0,0 +1,21 @@
|
|||
<div class="calendar-widget basic-widget">
|
||||
<div class="dashboard-calendar"
|
||||
data-month-events-url="<%= dashboard_calendar_path %>"
|
||||
data-day-events-url="<%= day_dashboard_calendar_path %>"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
<%
|
||||
js_format = I18n.backend.date_format.dup
|
||||
js_format.gsub!(/%-d/, 'D')
|
||||
js_format.gsub!(/%d/, 'DD')
|
||||
js_format.gsub!(/%-m/, 'M')
|
||||
js_format.gsub!(/%m/, 'MM')
|
||||
js_format.gsub!(/%b/, 'MMM')
|
||||
js_format.gsub!(/%B/, 'MMMM')
|
||||
js_format.gsub!('%Y', 'YYYY')
|
||||
%>
|
||||
|
||||
var formatJS = "<%= js_format %>"
|
||||
</script>
|
57
app/views/dashboards/_create_task_modal.html.erb
Normal file
57
app/views/dashboards/_create_task_modal.html.erb
Normal file
|
@ -0,0 +1,57 @@
|
|||
<div class="modal" id="create-task-modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
|
||||
<h2 class="modal-title">
|
||||
<%= t("dashboard.create_task_modal.title") %>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="description">
|
||||
<%= t("dashboard.create_task_modal.description") %>
|
||||
</div>
|
||||
<div class="select-block" data-error-object="Project" data-error="">
|
||||
<label><%= t("dashboard.create_task_modal.project") %></label>
|
||||
<select class="project-filter"
|
||||
data-ajax-url="<%= dashboard_quick_start_project_filter_path %>"
|
||||
data-placeholder="<%= t("dashboard.create_task_modal.project_placeholder") %>"></select>
|
||||
</div>
|
||||
<div class="new-projects-visibility" style="display: none;">
|
||||
<div class="down-arrow"></div>
|
||||
<div class="project-visibility-container">
|
||||
<div class="project-visibility-title"><%= t("dashboard.create_task_modal.project_visibility_label") %></div>
|
||||
<div class="sci-toggles-group" data-toggle="buttons">
|
||||
<input type="radio" name="projects-visibility-selector" class="sci-toggle-item" value="hidden" checked="checked">
|
||||
<label class="sci-toggle-item-label">
|
||||
<%= t("dashboard.create_task_modal.project_visibility_members") %>
|
||||
</label>
|
||||
<input type="radio" name="projects-visibility-selector" class="sci-toggle-item" value="visible">
|
||||
<label class="sci-toggle-item-label">
|
||||
<%= t("dashboard.create_task_modal.project_visibility_all") %>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="select-block" data-error-object="Experiment" data-error="">
|
||||
<label><%= t("dashboard.create_task_modal.experiment") %></label>
|
||||
<select class="experiment-filter"
|
||||
data-ajax-url="<%= dashboard_quick_start_experiment_filter_path %>"
|
||||
data-disable-on-load="true"
|
||||
data-disable-placeholder="<%= t("dashboard.create_task_modal.experiment_disabled_placeholder") %>"
|
||||
data-placeholder="<%= t("dashboard.create_task_modal.experiment_placeholder") %>"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button"
|
||||
class="create-task-button btn btn-primary"
|
||||
data-ajax-url="<%= dashboard_quick_start_create_task_path %>"
|
||||
data-dismiss="modal"
|
||||
disabled>
|
||||
<%= t("dashboard.create_task_modal.create_task") %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
66
app/views/dashboards/_current_tasks.html.erb
Normal file
66
app/views/dashboards/_current_tasks.html.erb
Normal file
|
@ -0,0 +1,66 @@
|
|||
<div class="current-tasks-widget basic-widget">
|
||||
<div class="widget-header">
|
||||
<div class="widget-title"><%= t("dashboard.current_tasks.title") %></div>
|
||||
<div class="actions-container">
|
||||
<div class="filter-container dropdown">
|
||||
<div class="btn btn-light icon-btn filter-button" data-toggle="dropdown"><i class="fas fa-filter"></i></div>
|
||||
<div class="dropdown-menu curent-tasks-filters" role="menu">
|
||||
<div class="header">
|
||||
<div class="title"><%= t("dashboard.current_tasks.filter.title") %></div>
|
||||
<div class="btn btn-light clear-button"><i class="fas fa-times-circle"></i><%= t("dashboard.current_tasks.filter.clear") %></div>
|
||||
</div>
|
||||
<div class="select-block">
|
||||
<label><%= t("dashboard.current_tasks.filter.sort") %></label>
|
||||
<select class="sort-filter">
|
||||
<option value="date_asc" ><%= t("dashboard.current_tasks.filter.date_asc") %></option>
|
||||
<option value="date_desc" ><%= t("dashboard.current_tasks.filter.date_desc") %></option>
|
||||
<option value="atoz" ><%= t("dashboard.current_tasks.filter.atoz") %></option>
|
||||
<option value="ztoa" ><%= t("dashboard.current_tasks.filter.ztoa") %></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select-block">
|
||||
<label><%= t("dashboard.current_tasks.filter.display") %></label>
|
||||
<select class="view-filter">
|
||||
<option value="uncompleted" ><%= t("dashboard.current_tasks.filter.uncompleted_tasks") %></option>
|
||||
<option value="completed" ><%= t("dashboard.current_tasks.filter.completed_tasks") %></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="select-block">
|
||||
<label><%= t("dashboard.current_tasks.filter.project") %></label>
|
||||
<select class="project-filter"
|
||||
data-ajax-url="<%= project_filter_dashboard_current_tasks_path %>"
|
||||
data-placeholder="<%= t("dashboard.current_tasks.filter.select_project") %>"></select>
|
||||
</div>
|
||||
<div class="select-block">
|
||||
<label><%= t("dashboard.current_tasks.filter.experiment") %></label>
|
||||
<select class="experiment-filter"
|
||||
data-ajax-url="<%= experiment_filter_dashboard_current_tasks_path %>"
|
||||
data-disable-on-load="true"
|
||||
data-disable-placeholder="<%= t("dashboard.current_tasks.filter.select_experiment") %>"
|
||||
data-placeholder="<%= t("dashboard.current_tasks.filter.select_experiment") %>"></select>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="btn btn-primary apply-filters"><%= t("dashboard.current_tasks.filter.apply") %></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-container">
|
||||
<div class="sci-input-container left-icon ">
|
||||
<input type="text" class="sci-input-field task-search-field" placeholder="<%= t("dashboard.current_tasks.search") %>"></input>
|
||||
<i class="fas fa-search"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sci-secondary-navbar current-tasks-navbar">
|
||||
<a class="navbar-link navbar-assigned active" href="" data-remote="true" data-mode="assigned"><%= t("dashboard.current_tasks.navbar.assigned") %></a>
|
||||
<a class="navbar-link navbar-all" href="" data-remote="true" data-mode="team"><%= t("dashboard.current_tasks.navbar.all") %></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-body">
|
||||
<div class="current-tasks-list perfect-scrollbar"
|
||||
data-tasks-list-url="<%= dashboard_current_tasks_path %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
20
app/views/dashboards/_quick_start.html.erb
Normal file
20
app/views/dashboards/_quick_start.html.erb
Normal file
|
@ -0,0 +1,20 @@
|
|||
<div class="quick-start-widget basic-widget">
|
||||
<div class="widget-header">
|
||||
<div class="widget-title">
|
||||
<%= t("dashboard.quick_start.title") %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="widget-body">
|
||||
<div class="quick-start-description">
|
||||
<%= t("dashboard.quick_start.description") %>
|
||||
</div>
|
||||
<div class="new-task btn btn-secondary btn-block"><i class="fas fa-plus"></i><%= t("dashboard.quick_start.new_task") %></div>
|
||||
<%= link_to protocols_path(new_protocol: true), {class: "new-protocol btn btn-secondary btn-block"} do %>
|
||||
<i class="fas fa-edit"></i><%= t("dashboard.quick_start.new_protocol") %>
|
||||
<% end %>
|
||||
<%= link_to reports_path(new_report: true), {class: "new-report btn btn-secondary btn-block"} do %>
|
||||
<i class="fas fa-clipboard-check"></i><%= t("dashboard.quick_start.new_report") %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render "create_task_modal" %>
|
2
app/views/dashboards/_recent_work.html.erb
Normal file
2
app/views/dashboards/_recent_work.html.erb
Normal file
|
@ -0,0 +1,2 @@
|
|||
<div class="recent-work-widget basic-widget">
|
||||
</div>
|
8
app/views/dashboards/show.html.erb
Normal file
8
app/views/dashboards/show.html.erb
Normal file
|
@ -0,0 +1,8 @@
|
|||
<% provide :head_title, t('nav.label.dashboard') %>
|
||||
|
||||
<div class="dashboard-container">
|
||||
<%= render "calendar" %>
|
||||
<%= render "current_tasks" %>
|
||||
<%= render "recent_work" %>
|
||||
<%= render "quick_start" %>
|
||||
</div>
|
|
@ -5,7 +5,7 @@
|
|||
<% provide(:head_title, t("protocols.index.head_title")) %>
|
||||
|
||||
<% if current_team %>
|
||||
<div class="content-pane" id="protocols-index">
|
||||
<div class="content-pane" id="protocols-index" <%= "data-new-protocol=true" if params[:new_protocol] %>>
|
||||
<ul class="nav nav-tabs nav-settings">
|
||||
<li role="presentation" class="<%= "active" if @type == :public %>">
|
||||
<%= link_to t("protocols.index.navigation.public"), protocols_path(team: @current_team, type: :public) %>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<%= stylesheet_link_tag 'datatables' %>
|
||||
|
||||
<div class="content-pane">
|
||||
<div id="content-reports-index">
|
||||
<div id="content-reports-index" <%= "data-new-report=true" if params[:new_report] %>>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<% if can_manage_reports?(current_team) %>
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
<div class="scroll-wrapper">
|
||||
<ul class="nav">
|
||||
<% if current_user.teams.empty? %>
|
||||
<li class="disabled">
|
||||
<span>
|
||||
<span class="fas fa-thumbtack"></span>
|
||||
<span><%= t('left_menu_bar.dashboard') %></span>
|
||||
</span>
|
||||
</li>
|
||||
<li class="disabled">
|
||||
<span>
|
||||
<span class="fas fa-folder"></span>
|
||||
|
@ -27,6 +33,12 @@
|
|||
</span>
|
||||
</li>
|
||||
<% else %>
|
||||
<li class="<%= "active" if dashboard_are_selected? %>">
|
||||
<%= link_to dashboard_path, id: "dashboard-link", title: t('left_menu_bar.dashboard') do %>
|
||||
<span class="fas fa-thumbtack"></span>
|
||||
<span><%= t('left_menu_bar.dashboard') %></span>
|
||||
<% end %>
|
||||
</li>
|
||||
<li class="<%= "active" if projects_are_selected? %>">
|
||||
<%= link_to projects_path, id: "projects-link", title: t('left_menu_bar.projects') do %>
|
||||
<span class="fas fa-folder"></span>
|
||||
|
|
19
app/views/shared/_my_modules_list_partial.html.erb
Normal file
19
app/views/shared/_my_modules_list_partial.html.erb
Normal file
|
@ -0,0 +1,19 @@
|
|||
<div class="my-modules-list-partial">
|
||||
<% task_groups.each do |task_group| %>
|
||||
<div class="task-group">
|
||||
<div class="header">
|
||||
<span class="project" title="<%= task_group[:project_name] %>"><%= task_group[:project_name] %></span>
|
||||
<span class="slash">/</span>
|
||||
<span class="experiment" title="<%= task_group[:experiment_name] %>"><%= task_group[:experiment_name] %></span>
|
||||
</div>
|
||||
<div class="tasks">
|
||||
<% task_group[:tasks].each do |task| %>
|
||||
<div class="task">
|
||||
<%= draw_custom_icon('task-icon') %>
|
||||
<%= link_to(task[:task_name], protocols_my_module_path(task[:id]), {class: "task-link", title: task[:task_name]}) %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
|
@ -56,16 +56,14 @@ class Constants
|
|||
COMMENTS_SEARCH_LIMIT = 10
|
||||
# Activity limited query/display elements for pages
|
||||
ACTIVITY_AND_NOTIF_SEARCH_LIMIT = 20
|
||||
|
||||
# Infinite Scroll load limit (elements per page)
|
||||
INFINITE_SCROLL_LIMIT = 20
|
||||
# Maximum number of users that can be invited in a single action
|
||||
INVITE_USERS_LIMIT = 20
|
||||
|
||||
# Maximum nr. of search results for atwho (smart annotations)
|
||||
ATWHO_SEARCH_LIMIT = 5
|
||||
|
||||
# Max characters for repository name in Atwho modal
|
||||
ATWHO_REP_NAME_LIMIT = 16
|
||||
|
||||
# Number of protocols in recent protocol dropdown
|
||||
RECENT_PROTOCOL_LIMIT = 14
|
||||
|
||||
|
|
62
config/locales/dashboard/en.yml
Normal file
62
config/locales/dashboard/en.yml
Normal file
|
@ -0,0 +1,62 @@
|
|||
en:
|
||||
dashboard:
|
||||
current_tasks:
|
||||
title: "Current tasks"
|
||||
search: "Search tasks"
|
||||
navbar:
|
||||
assigned: "Assigned to me"
|
||||
all: "All"
|
||||
filter:
|
||||
title: "Filters"
|
||||
clear: "Clear"
|
||||
sort: "Sort by"
|
||||
date_asc: "Due Date Ascending"
|
||||
date_desc: "Due Date Descending"
|
||||
atoz: "From A to Z"
|
||||
ztoa: "From Z to A"
|
||||
display: "Display"
|
||||
uncompleted_tasks: "Tasks in progress"
|
||||
completed_tasks: "Tasks completed"
|
||||
project: "Project"
|
||||
select_project: "All Projects"
|
||||
experiment: "Experiment"
|
||||
select_experiment: "All Experiments"
|
||||
apply: "Apply"
|
||||
no_tasks:
|
||||
text_1: "Looks like you completed all of your tasks. Good job!"
|
||||
text_2: "Why not use use the Quick start section to create your next task"
|
||||
due_date: "Due date: %{date}"
|
||||
progress_bar:
|
||||
in_progress: "In progress"
|
||||
overdue: "Overdue"
|
||||
completed_steps: ": %{steps} / %{total_steps}"
|
||||
completed: "Completed"
|
||||
calendar:
|
||||
due_on: Due on
|
||||
dow:
|
||||
su: 'Su'
|
||||
mo: 'Mo'
|
||||
tu: 'Tu'
|
||||
we: 'We'
|
||||
th: 'Th'
|
||||
fr: 'Fr'
|
||||
sa: 'Sa'
|
||||
quick_start:
|
||||
title: "Quick start"
|
||||
description: "Quickly start a new task, create a new project or a new report."
|
||||
new_task: "New task"
|
||||
new_protocol: "New protocol"
|
||||
new_report: "New report"
|
||||
create_task_modal:
|
||||
title: "Create a new Task"
|
||||
description: "Simply type in the fields bellow to find or create space for your new task to live in"
|
||||
project: "Project"
|
||||
project_visibility_label: "Visible to"
|
||||
project_visibility_members: "Project members"
|
||||
project_visibility_all: "All team members"
|
||||
experiment: "Experiment"
|
||||
project_placeholder: "Enter project name (New or Existing)"
|
||||
experiment_placeholder: "Enter experiment name (New or Existing)"
|
||||
experiment_disabled_placeholder: "Select Project to enable Experiments"
|
||||
filter_create_new: "Create"
|
||||
create_task: "Create Task"
|
|
@ -134,7 +134,8 @@ en:
|
|||
none: "No activities!"
|
||||
label:
|
||||
scinote: "SciNote"
|
||||
projects: "Home"
|
||||
dashboard: "Overview"
|
||||
projects: "Projects"
|
||||
protocols: "Protocols"
|
||||
calendar: "Calendar"
|
||||
activities: "Activities"
|
||||
|
@ -151,6 +152,7 @@ en:
|
|||
addon_versions: "Addon versions"
|
||||
|
||||
left_menu_bar:
|
||||
dashboard: "Overview"
|
||||
projects: "Projects"
|
||||
repositories: "Inventories"
|
||||
templates: "Protocols"
|
||||
|
@ -258,7 +260,7 @@ en:
|
|||
|
||||
projects:
|
||||
index:
|
||||
head_title: "Home"
|
||||
head_title: "Projects"
|
||||
archive: "Archive"
|
||||
archived: "Archived"
|
||||
active: "Active"
|
||||
|
@ -2113,6 +2115,9 @@ en:
|
|||
dropdown_selector:
|
||||
nothing_found: "Nothing found..."
|
||||
|
||||
create_task_service:
|
||||
default_task_name: "New Task"
|
||||
|
||||
zip_export:
|
||||
modal_label: 'Export inventory'
|
||||
notification_title: 'Your requested export package is ready!'
|
||||
|
|
|
@ -16,7 +16,7 @@ Rails.application.routes.draw do
|
|||
confirmations: 'users/confirmations',
|
||||
omniauth_callbacks: 'users/omniauth_callbacks' }
|
||||
|
||||
root 'projects#index'
|
||||
root 'dashboards#show'
|
||||
|
||||
# # Client APP endpoints
|
||||
# get '/settings', to: 'client_api/settings#index'
|
||||
|
@ -240,6 +240,23 @@ Rails.application.routes.draw do
|
|||
defaults: { format: 'json' }
|
||||
post 'reports/destroy', to: 'reports#destroy'
|
||||
|
||||
resource :dashboard, only: :show do
|
||||
resource :current_tasks, module: 'dashboard', only: :show do
|
||||
get :project_filter
|
||||
get :experiment_filter
|
||||
end
|
||||
|
||||
namespace :quick_start, module: :dashboard, controller: :quick_start do
|
||||
get :project_filter
|
||||
get :experiment_filter
|
||||
post :create_task
|
||||
end
|
||||
|
||||
resource :calendar, module: 'dashboard', only: [:show] do
|
||||
get :day
|
||||
end
|
||||
end
|
||||
|
||||
resources :projects, except: [:new, :destroy] do
|
||||
resources :user_projects, path: '/users',
|
||||
only: [:create, :index, :update, :destroy]
|
||||
|
|
6
vendor/assets/javascripts/clndr.min.js
vendored
Normal file
6
vendor/assets/javascripts/clndr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue