Add favorite to project table [SCI-11800]

This commit is contained in:
Andrej 2025-05-07 15:28:51 +02:00
parent 72f549f00b
commit fe676f8996
7 changed files with 144 additions and 2 deletions

View file

@ -27,6 +27,7 @@
@access="access"
@updateDueDate="updateDueDate"
@updateStartDate="updateStartDate"
@updateFavorite="updateFavorite"
>
<template #card="data">
<ProjectCard :params="data.params" :dtComponent="data.dtComponent" ></ProjectCard>
@ -93,6 +94,7 @@ import NewFolderModal from './modals/new_folder.vue';
import MoveModal from './modals/move.vue';
import AccessModal from '../shared/access_modal/modal.vue';
import ExportLimitExceededModal from './modals/export_limit_exceeded_modal.vue';
import FavoriteRenderer from '../shared/datatable/renderers/favorite.vue';
export default {
name: 'ProjectsList',
@ -112,7 +114,8 @@ export default {
DescriptionRenderer,
DescriptionModal,
StatusRenderer,
SuperviserRenderer
SuperviserRenderer,
FavoriteRenderer
},
props: {
dataSource: { type: String, required: true },
@ -158,6 +161,17 @@ export default {
sortable: true,
cellRenderer: 'NameRenderer'
},
{
field: 'favorite',
headerComponentParams: {
html: '<div class="sn-icon sn-icon-star-filled"></div>'
},
headerName: this.i18n.t('projects.index.favorite'),
sortable: true,
cellRenderer: FavoriteRenderer,
minWidth: 80,
notSelectable: true
},
{
field: 'code',
headerName: this.i18n.t('projects.index.card.id'),
@ -365,6 +379,12 @@ export default {
this.updateTable();
});
},
updateFavorite(value, params) {
const url = value ? params.data.urls.favorite : params.data.urls.unfavorite;
axios.post(url).then(() => {
this.updateTable();
});
},
showDescription(_e, project) {
[this.descriptionModalObject] = project;
},

View file

@ -0,0 +1,43 @@
<template>
<div v-if="hasFavorite">
<button @click="updateFavorite(!favorite)"
class="flex justify-center items-center w-full h-9 bg-transparent border-none cursor-pointer">
<div v-if="favorite" class="sn-icon sn-icon-star-filled text-sn-alert-brittlebush"></div>
<div v-else @mouseover="hovered = true"
@mouseleave="hovered = false">
<div class="text-sn-grey-500 sn-icon" :class="{ 'sn-icon-star-filled': hovered, 'sn-icon-star': !hovered }"></div>
</div>
</button>
</div>
</template>
<script>
export default {
name: 'favoriteRenderer',
props: {
params: {
required: true
}
},
data() {
return {
favorite: false,
hovered: false
};
},
created() {
this.favorite = this.params.data.favorite;
},
computed: {
hasFavorite() {
return this.params.data.favorite !== null;
}
},
methods: {
updateFavorite(value) {
this.params.dtComponent.$emit('updateFavorite', value, this.params);
this.favorite = value;
}
}
};
</script>

View file

@ -77,6 +77,7 @@ module UserAssignments
assigned_by: @assigned_by
)
else
object.favorites.where(user: @user).destroy_all if object.respond_to?(:favorites)
user_assignment.destroy!
end
end

View file

@ -8,7 +8,7 @@ module Lists
attributes :name, :code, :created_at, :archived_on, :users, :urls, :folder, :hidden,
:folder_info, :default_public_user_role_id, :team, :top_level_assignable, :supervised_by,
:comments, :updated_at, :permissions, :due_date_cell, :start_on_cell, :description, :status,
:comments, :updated_at, :permissions, :due_date_cell, :start_on_cell, :description, :status, :favorite,
def team
object.team.name
end
@ -17,6 +17,10 @@ module Lists
!project?
end
def favorite
object.favorite if project?
end
def top_level_assignable
project?
end

View file

@ -148,6 +148,10 @@ module Lists
@records = @records.sort_by { |object| project_comments_count(object) }
when 'comments_DESC'
@records = @records.sort_by { |object| project_comments_count(object) }.reverse!
when 'favorite_ASC'
@records = @records.sort_by { |object| project_favorites(object) }
when 'favorite_DESC'
@records = @records.sort_by { |object| project_favorites(object) }.reverse!
end
end
@ -163,6 +167,14 @@ module Lists
project?(object) ? object.users.count : -1
end
def project_favorites(object)
if project?(object)
object.favorite ? 1 : 0
else
-1
end
end
def project?(object)
object.instance_of?(Project)
end

View file

@ -655,6 +655,7 @@ en:
add_start_date: "Add start date"
no_start_date: "No start date"
add_description: 'Add description'
favorite: 'Favorites'
status:
not_started: "Not started"
started: "In progress"

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class CreateFavorites < ActiveRecord::Migration[7.0]
STATE_NAMES = %w(ProjectList_active_state ProjectList_archived_state ExperimentList_active_state
ExperimentList_archived_state MyModuleList_active_state MyModuleList_archived_state).freeze
def change
create_table :favorites do |t|
t.references :user, null: false, foreign_key: true
@ -16,5 +18,64 @@ class CreateFavorites < ActiveRecord::Migration[7.0]
unique: true,
name: :index_favorites_on_user_and_item_and_team
)
reversible do |dir|
dir.up do
User.find_each do |user|
update = false
STATE_NAMES.each do |state_name|
state = user.settings.dig(state_name, 'columnsState')
next if state.blank?
update = true
# get number of pinned columns
pinned_items_count = state.count { |el| el['pinned'].present? }
# update position of columns if they are not pinned
state.each do |el|
el['position'] += 1 if el['pinned'].blank? && el['position'].present?
end
# create new state of favorite column
new_element = {
colId: 'favorite',
hide: false,
pinned: nil
}
# add only position if other columns have positions. Position only get columns for which user at least once change settings
new_element[:position] = pinned_items_count if state.dig(0, 'position').present?
state.insert(pinned_items_count, new_element)
end
user.save! if update
end
end
dir.down do
User.find_each do |user|
update = false
STATE_NAMES.each do |state_name|
state = user.settings.dig(state_name, 'columnsState')
next if state.blank?
update = true
favorite = state.find { |el| el['colId'] == 'favorite' }
next unless favorite
state.reject! { |el| el['colId'] == 'favorite' }
next if favorite['position'].blank?
state.each do |el|
el['position'] -= 1 if el['position'].present? && el['position'] > favorite['position']
end
end
user.save! if update
end
end
end
end
end