Merge pull request #8490 from aignatov-bio/ai-sci-11854-add-favorites-widget

Add favorites widget [SCI-11854]
This commit is contained in:
aignatov-bio 2025-05-09 11:29:01 +02:00 committed by GitHub
commit 5a0ebdef67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 215 additions and 5 deletions

View file

@ -27,6 +27,13 @@ var DasboardCurrentTasksWidget = (function() {
return values;
}
function initFavorites() {
$('.current-tasks-widget .actions-container').addClass('hidden');
$('.current-tasks-list-wrapper').addClass('hidden');
$('#favoritesWidget').removeClass('hidden');
window.favoritesWidget.$refs.widget.resetFavorites();
}
function resetMarkAppliedFilters() {
$('.filter-container').removeClass('filters-applied');
}
@ -84,6 +91,10 @@ var DasboardCurrentTasksWidget = (function() {
$('.current-tasks-navbar .navbar-link').removeClass('active');
$('.current-tasks-navbar').find(`[data-mode='${parsedFilterState.mode}']`).addClass('active');
markAppliedFilters();
if (parsedFilterState.mode === 'favorites') {
initFavorites();
}
} catch (e) {
dropdownSelector.selectValues(statusFilter, getDefaultStatusValues());
resetMarkAppliedFilters();
@ -235,7 +246,14 @@ var DasboardCurrentTasksWidget = (function() {
$('.current-tasks-navbar .navbar-link').on('click', function() {
$(this).parent().find('.navbar-link').removeClass('active');
$(this).addClass('active');
loadCurrentTasksList(true);
if (this.dataset.mode === 'favorites') {
initFavorites();
} else {
$('.current-tasks-widget .actions-container').removeClass('hidden');
$('.current-tasks-list-wrapper').removeClass('hidden');
$('#favoritesWidget').addClass('hidden');
loadCurrentTasksList(true);
}
filterStateSave();
});
}

View file

@ -20,6 +20,11 @@ module Dashboard
render json: { data: tasks_list, next_page: tasks.next_page }
end
def favorites
favorites = current_user.favorites.includes(:item).order(created_at: :desc).page(params[:page]).per(Constants::INFINITE_SCROLL_LIMIT)
render json: favorites, each_serializer: FavoriteSerializer, meta: pagination_dict(favorites)
end
def project_filter
projects = current_team.projects
.where(archived: false)

View file

@ -0,0 +1,8 @@
import { createApp } from 'vue/dist/vue.esm-bundler.js';
import FavoritesWidget from '../../vue/favorites/widget.vue';
import { mountWithTurbolinks } from './helpers/turbolinks.js';
const app = createApp();
app.component('FavoritesWidget', FavoritesWidget);
app.config.globalProperties.i18n = window.I18n;
window.favoritesWidget = mountWithTurbolinks(app, '#favoritesWidget');

View file

@ -0,0 +1,95 @@
<template>
<div ref="scrollContainer" class="h-full overflow-y-auto">
<div v-if="favorites.length > 0" v-for="favorite in favorites" class="px-4">
<a :href="favorite.attributes.url" class="flex text-black items-center gap-2 py-2 hover:no-underline">
<i class="sn-icon sn-icon-star-filled text-sn-alert-brittlebush" style="font-size: 32px !important"></i>
<div>
<div class="flex items-center gap-2 text-sn-grey">
<template v-for="(breadcrumb, index) in favorite.attributes.breadcrumbs" :key="index" >
<div v-if="index > 0">
<span v-if="index > 1"> / </span>
<span v-if="index + 1 < favorite.attributes.breadcrumbs.length" class="text-xs">
{{ breadcrumb.name }}
</span>
<span v-else class="text-xs">
{{ favorite.attributes.code }}
</span>
</div>
</template>
</div>
<div class="font-bold text-base">{{ favorite.attributes.name }}</div>
</div>
<div
class="ml-auto rounded px-1.5 py-1 mt-4"
:class="{
'text-black border': favorite.attributes.status.light_color,
'text-white': !favorite.attributes.status.light_color,
}"
:style="{ backgroundColor: favorite.attributes.status.color }"
>
{{ favorite.attributes.status.name }}
</div>
</a>
<hr class="my-0">
</div>
<h2 v-else-if="!loading" class="ml-6">
{{ i18n.t('dashboard.current_tasks.no_tasks.favorites') }}
</h2>
</div>
</template>
<script>
import axios from '../../packs/custom_axios.js';
export default {
name: 'FavoritesWidget',
props: {
favoritesUrl: {
type: String,
required: true
}
},
data() {
return {
favorites: [],
page: 1,
loading: true
};
},
mounted() {
const activeTab = document.querySelector('.current-tasks-navbar .navbar-link.active');
if (activeTab.dataset.mode === 'favorites') {
this.resetFavorites();
}
this.$refs.scrollContainer.addEventListener('scroll', () => {
if (this.$refs.scrollContainer.scrollTop + this.$refs.scrollContainer.clientHeight >= this.$refs.scrollContainer.scrollHeight - 100) {
this.loadFavorites();
}
});
},
methods: {
resetFavorites() {
this.favorites = [];
this.page = 1;
this.loading = false;
this.loadFavorites();
},
loadFavorites() {
if (this.loading || !this.page) return;
this.loading = true;
axios.get(this.favoritesUrl, { params: { page: this.page } })
.then((response) => {
this.favorites = this.favorites.concat(response.data.data);
if (response.data.data.length > 0) {
this.page += 1;
} else {
this.page = null;
}
this.loading = false;
});
}
}
};
</script>

View file

@ -0,0 +1,65 @@
# frozen_string_literal: true
class FavoriteSerializer < ActiveModel::Serializer
include Canaid::Helpers::PermissionsHelper
include Rails.application.routes.url_helpers
include BreadcrumbsHelper
attributes :id, :name, :code, :status, :breadcrumbs, :url
def code
object.item.code
end
def name
object.item.name
end
def breadcrumbs
subject = object.item
generate_breadcrumbs(subject, []) if subject
end
def url
case object.item_type
when 'Project'
experiments_path(project_id: object.item)
when 'Experiment'
my_modules_experiment_path(object.item)
when 'MyModule'
protocols_my_module_path(object.item)
end
end
def status
case object.item_type
when 'MyModule'
{
name: object.item.my_module_status.name,
color: object.item.my_module_status.color,
light_color: object.item.my_module_status.light_color?
}
else
case object.item.status
when :not_started
{
name: I18n.t('projects.index.status.not_started'),
color: Constants::STATUS_COLORS[:not_started],
light_color: true
}
when :started
{
name: I18n.t('projects.index.status.started'),
color: Constants::STATUS_COLORS[:started],
light_color: false
}
when :completed
{
name: I18n.t('projects.index.status.completed'),
color: Constants::STATUS_COLORS[:completed],
light_color: false
}
end
end
end
end

View file

@ -69,7 +69,8 @@
</div>
</div>
<div class="sci-secondary-navbar current-tasks-navbar">
<div class="sci-secondary-navbar current-tasks-navbar ml-auto">
<span class="navbar-link" data-mode="favorites"><%= t("dashboard.current_tasks.navbar.favorites") %></span>
<span class="navbar-link active" data-mode="assigned"><%= t("dashboard.current_tasks.navbar.assigned") %></span>
<span class="navbar-link" data-mode="team"><%= t("dashboard.current_tasks.navbar.all") %></span>
</div>
@ -81,6 +82,12 @@
data-tasks-list-url="<%= dashboard_current_tasks_path %>">
</div>
</div>
<div id="favoritesWidget" class="hidden h-full">
<favorites-widget
ref="widget"
favorites-url="<%= favorites_dashboard_current_tasks_path %>"
></favorites-widget>
</div>
</div>
</div>
@ -97,3 +104,5 @@
<p class="widget-placeholder-title"><%= I18n.t('dashboard.current_tasks.no_tasks.search_result.title') %></p>
</div>
</template>
<%= javascript_include_tag 'vue_favorites_widget' %>

View file

@ -443,6 +443,12 @@ class Constants
FAST_STATUS_POLLING_INTERVAL = 5000
SLOW_STATUS_POLLING_INTERVAL = 10000
STATUS_COLORS = {
not_started: '#fffff',
started: '#3070ED',
completed: '#5EC66F'
}
# Interval time for polling asset changes when editing with SciNote Edit
ASSET_POLLING_INTERVAL = 5000

View file

@ -1,9 +1,10 @@
en:
dashboard:
current_tasks:
title: "Current tasks"
title: "Current work"
search: "Search tasks"
navbar:
favorites: "Favorites"
assigned: "Assigned to me"
all: "All"
filter:
@ -32,6 +33,7 @@ en:
apply: "Apply"
show_results: "Show results"
no_tasks:
favorites: "Projects, experiments and tasks you mark as favorites appear here."
assigned_tasks:
title: "There are no tasks assigned to you at the moment."
description: "Use the “+ New task” button above to create a new one, or assign yourself to an existing task."
@ -83,7 +85,7 @@ en:
new_project: "Create \"%{name}\" Project"
new_experiment: "Create \"%{name}\" Experiment"
recent_work:
title: "Recent work"
title: "My recent activity"
no_results:
title: "You have not worked on anything recently."
description: "All tasks, projects, inventories protocols and reports, you last worked on will appear here, when you make some changes."

View file

@ -301,6 +301,7 @@ Rails.application.routes.draw do
resource :current_tasks, module: 'dashboard', only: :show do
get :project_filter
get :experiment_filter
get :favorites
end
namespace :quick_start, module: :dashboard, controller: :quick_start do

View file

@ -72,7 +72,8 @@ const entryList = {
vue_form_table: './app/javascript/packs/vue/forms_table.js',
vue_my_module_assigned_items: './app/javascript/packs/vue/my_module_assigned_items.js',
vue_design_system_inputs: './app/javascript/packs/vue/design_system/inputs.js',
vue_design_system_table: './app/javascript/packs/vue/design_system/table.js'
vue_design_system_table: './app/javascript/packs/vue/design_system/table.js',
vue_favorites_widget: './app/javascript/packs/vue/favorites_widget.js'
};
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949