mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-11-17 14:32:34 +08:00
Add table to design elements [SCI-11810]
This commit is contained in:
parent
8547b61ea4
commit
886a849b06
13 changed files with 200 additions and 188 deletions
|
|
@ -1,29 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DesignElementsController < ApplicationController
|
||||
def index
|
||||
end
|
||||
def index; end
|
||||
|
||||
def test_select
|
||||
render json: { data: [
|
||||
['1', 'One'],
|
||||
['2', 'Two'],
|
||||
['3', 'Three'],
|
||||
['4', 'Four'],
|
||||
['5', 'Five'],
|
||||
['6', 'Six'],
|
||||
['7', 'Seven'],
|
||||
['8', 'Eight'],
|
||||
['9', 'Nine'],
|
||||
['10', 'Ten']
|
||||
%w(1 One),
|
||||
%w(2 Two),
|
||||
%w(3 Three),
|
||||
%w(4 Four),
|
||||
%w(5 Five),
|
||||
%w(6 Six),
|
||||
%w(7 Seven),
|
||||
%w(8 Eight),
|
||||
%w(9 Nine),
|
||||
%w(10 Ten)
|
||||
].select { |item| item[1].downcase.include?(params[:query].downcase) } }
|
||||
end
|
||||
|
||||
def test_table
|
||||
render json: {
|
||||
data: [
|
||||
{ id: 1, attributes: {name: 'One' } },
|
||||
{ id: 2, attributes: {name: 'Two' } },
|
||||
{ id: 3, attributes: {name: 'Three' } },
|
||||
{ id: 4, attributes: {name: 'Four' } },
|
||||
{ id: 1, attributes: {
|
||||
name: 'One',
|
||||
description: nil,
|
||||
date: {
|
||||
value: I18n.l(DateTime.now, format: :default),
|
||||
value_formatted: I18n.l(DateTime.now, format: :full_date),
|
||||
editable: true
|
||||
}
|
||||
} },
|
||||
{ id: 2, attributes: { name: 'Two', description: '[@admin~1]', date: { editable: true } } },
|
||||
{ id: 3,
|
||||
attributes: { name: 'Three', description: 'Long long long long name Long long long long name Long long long long name Long long long long name',
|
||||
date: { editable: true } } },
|
||||
{ id: 4, attributes: { name: 'Four', date: { editable: true } } }
|
||||
],
|
||||
meta: {
|
||||
current_page: 1,
|
||||
|
|
|
|||
|
|
@ -1,89 +1,60 @@
|
|||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import DataTable from '../shared/datatable/table.vue';
|
||||
import DataTable from '../../../vue/shared/datatable/table.vue';
|
||||
import DescriptionRenderer from '../../../vue/shared/datatable/renderers/description.vue';
|
||||
import DateRenderer from '../../../vue/shared/datatable/renderers/date.vue';
|
||||
import { mountWithTurbolinks } from '../helpers/turbolinks.js';
|
||||
|
||||
const app = createApp({
|
||||
data() {
|
||||
return {
|
||||
dataUrl: '/design_elements/test_table'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
columnDefs() {
|
||||
const columns = [{
|
||||
field: 'name',
|
||||
flex: 1,
|
||||
headerName: this.i18n.t('projects.index.card.name'),
|
||||
sortable: true,
|
||||
cellRenderer: 'NameRenderer'
|
||||
}]
|
||||
const columns = [
|
||||
{
|
||||
field: 'name',
|
||||
flex: 1,
|
||||
headerName: 'Name',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
flex: 1,
|
||||
headerName: 'Description',
|
||||
sortable: true,
|
||||
cellRenderer: DescriptionRenderer
|
||||
},
|
||||
{
|
||||
field: 'date',
|
||||
flex: 1,
|
||||
headerName: 'Date',
|
||||
sortable: true,
|
||||
cellRenderer: DateRenderer,
|
||||
cellRendererParams: {
|
||||
placeholder: 'Add date',
|
||||
field: 'date',
|
||||
mode: 'datetime',
|
||||
emptyPlaceholder: 'No date',
|
||||
emitAction: 'updateDate'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return columns;
|
||||
},
|
||||
viewRenders() {
|
||||
return [
|
||||
{ type: 'table' },
|
||||
{ type: 'cards' }
|
||||
];
|
||||
return [];
|
||||
},
|
||||
toolbarActions() {
|
||||
const left = [];
|
||||
if (this.createUrl && this.currentViewMode !== 'archived') {
|
||||
left.push({
|
||||
name: 'create',
|
||||
icon: 'sn-icon sn-icon-new-task',
|
||||
label: this.i18n.t('projects.index.new'),
|
||||
type: 'emit',
|
||||
path: this.createUrl,
|
||||
buttonStyle: 'btn btn-primary'
|
||||
});
|
||||
}
|
||||
if (this.createFolderUrl) {
|
||||
left.push({
|
||||
name: 'create_folder',
|
||||
icon: 'sn-icon sn-icon-folder',
|
||||
label: this.i18n.t('projects.index.new_folder'),
|
||||
type: 'emit',
|
||||
path: this.createFolderUrl,
|
||||
buttonStyle: 'btn btn-light'
|
||||
});
|
||||
}
|
||||
return {
|
||||
left,
|
||||
left: [],
|
||||
right: []
|
||||
};
|
||||
},
|
||||
filters() {
|
||||
const filters = [
|
||||
{
|
||||
key: 'query',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
type: 'DateRange',
|
||||
label: this.i18n.t('filters_modal.created_on.label')
|
||||
}
|
||||
];
|
||||
|
||||
if (this.currentViewMode === 'archived') {
|
||||
filters.push({
|
||||
key: 'archived_at',
|
||||
type: 'DateRange',
|
||||
label: this.i18n.t('filters_modal.archived_on.label')
|
||||
});
|
||||
}
|
||||
|
||||
filters.push({
|
||||
key: 'members',
|
||||
type: 'Select',
|
||||
optionsUrl: this.usersFilterUrl,
|
||||
optionRenderer: this.usersFilterRenderer,
|
||||
labelRenderer: this.usersFilterRenderer,
|
||||
label: this.i18n.t('projects.index.filters_modal.members.label'),
|
||||
placeholder: this.i18n.t('projects.index.filters_modal.members.placeholder')
|
||||
});
|
||||
|
||||
filters.push({
|
||||
key: 'folder_search',
|
||||
type: 'Checkbox',
|
||||
label: this.i18n.t('projects.index.filters_modal.folders.label')
|
||||
});
|
||||
const filters = [];
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
import RowMenuRenderer from '../shared/datatable/row_menu_renderer.vue';
|
||||
import CardSelectorMixin from '../shared/datatable/mixins/card_selector.js';
|
||||
import workflowImgMixin from './workflow_img_mixin.js';
|
||||
import Description from './renderers/description.vue';
|
||||
import Description from '../shared/datatable/renderers/description.vue';
|
||||
|
||||
export default {
|
||||
name: 'ProjectCard',
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
import DataTable from '../shared/datatable/table.vue';
|
||||
import DescriptionRenderer from './renderers/description.vue';
|
||||
import DescriptionRenderer from '../shared/datatable/renderers/description.vue';
|
||||
import ConfirmationModal from '../shared/confirmation_modal.vue';
|
||||
import CompletedTasksRenderer from './renderers/completed_tasks.vue';
|
||||
import NameRenderer from './renderers/name.vue';
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
@archive="archive"
|
||||
@restore="restore"
|
||||
@duplicate="duplicate"
|
||||
@updateDueDate="updateDueDate"
|
||||
@editTags="editTags"/>
|
||||
|
||||
<TagsModal v-if="tagsModalObject"
|
||||
|
|
@ -60,7 +61,7 @@ import ConfirmationModal from '../shared/confirmation_modal.vue';
|
|||
import NameRenderer from './renderers/name.vue';
|
||||
import ResultsRenderer from './renderers/results.vue';
|
||||
import StatusRenderer from './renderers/status.vue';
|
||||
import DueDateRenderer from './renderers/due_date.vue';
|
||||
import DueDateRenderer from '../shared/datatable/renderers/date.vue';
|
||||
import DesignatedUsers from './renderers/designated_users.vue';
|
||||
import TagsModal from './modals/tags.vue';
|
||||
import TagsRenderer from './renderers/tags.vue';
|
||||
|
|
@ -134,6 +135,13 @@ export default {
|
|||
headerName: this.i18n.t('experiments.table.column.due_date_html'),
|
||||
sortable: true,
|
||||
cellRenderer: DueDateRenderer,
|
||||
cellRendererParams: {
|
||||
placeholder: this.i18n.t('my_modules.details.no_due_date_placeholder'),
|
||||
field: 'due_date_cell',
|
||||
mode: 'datetime',
|
||||
emptyPlaceholder: this.i18n.t('my_modules.details.no_due_date'),
|
||||
emitAction: 'updateDueDate'
|
||||
},
|
||||
minWidth: 200
|
||||
},
|
||||
{
|
||||
|
|
@ -275,6 +283,25 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
updateDueDate(value, params) {
|
||||
axios.put(params.data.urls.update_due_date, {
|
||||
my_module: {
|
||||
due_date: this.formatDate(value)
|
||||
}
|
||||
}).then(() => {
|
||||
this.updateTable();
|
||||
});
|
||||
},
|
||||
formatDate(date) {
|
||||
if (!(date instanceof Date)) return null;
|
||||
|
||||
const y = date.getFullYear();
|
||||
const m = date.getMonth() + 1;
|
||||
const d = date.getDate();
|
||||
const hours = date.getHours();
|
||||
const mins = date.getMinutes();
|
||||
return `${y}/${m}/${d} ${hours}:${mins}`;
|
||||
},
|
||||
updateTable() {
|
||||
this.tagsModalObject = null;
|
||||
this.newModalOpen = false;
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
<template>
|
||||
<div class="flex relative items-center gap-2">
|
||||
<DateTimePicker
|
||||
v-if="this.params.data.urls.update_due_date"
|
||||
class="borderless-input -mt-[1px]"
|
||||
:defaultValue="dueDate"
|
||||
@change="updateDueDate"
|
||||
mode="datetime"
|
||||
:placeholder="i18n.t('my_modules.details.no_due_date_placeholder')"
|
||||
:customIcon="customIcon"
|
||||
:clearable="true"/>
|
||||
<template v-else-if="params.data.due_date_formatted ">
|
||||
<i :class="customIcon || 'sn-icon sn-icon-calendar'"></i>
|
||||
{{ params.data.due_date_formatted }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ i18n.t('my_modules.details.no_due_date') }}
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import DateTimePicker from '../../shared/date_time_picker.vue';
|
||||
|
||||
export default {
|
||||
name: 'DueDateRenderer',
|
||||
components: {
|
||||
DateTimePicker
|
||||
},
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dueDate: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.dueDate = new Date(this.params.data.due_date?.replace(/([^!\s])-/g, '$1/'));
|
||||
},
|
||||
computed: {
|
||||
customIcon() {
|
||||
if (this.params.data.due_date_status === 'overdue') {
|
||||
return 'sn-icon sn-icon-alert-warning text-sn-delete-red';
|
||||
}
|
||||
|
||||
if (this.params.data.due_date_status === 'one_day_prior') {
|
||||
return 'sn-icon sn-icon-alert-warning text-sn-alert-brittlebush';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateDueDate(value) {
|
||||
this.dueDate = value;
|
||||
axios.put(this.params.data.urls.update_due_date, {
|
||||
my_module: {
|
||||
due_date: this.formatDate(value)
|
||||
}
|
||||
}).then(() => {
|
||||
this.params.dtComponent.updateTable();
|
||||
});
|
||||
},
|
||||
formatDate(date) {
|
||||
if (!(date instanceof Date)) return null;
|
||||
|
||||
const y = date.getFullYear();
|
||||
const m = date.getMonth() + 1;
|
||||
const d = date.getDate();
|
||||
const hours = date.getHours();
|
||||
const mins = date.getMinutes();
|
||||
return `${y}/${m}/${d} ${hours}:${mins}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
51
app/javascript/vue/shared/datatable/renderers/date.vue
Normal file
51
app/javascript/vue/shared/datatable/renderers/date.vue
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<div class="flex relative items-center gap-2">
|
||||
<DateTimePicker
|
||||
v-if="this.params.data[this.params.field].editable"
|
||||
class="borderless-input -mt-[1px]"
|
||||
:defaultValue="date"
|
||||
@change="updateDate"
|
||||
:mode="this.params.mode || 'datetime'"
|
||||
:placeholder="this.params.placeholder"
|
||||
:customIcon="this.params.data[this.params.field].icon"
|
||||
:clearable="true"/>
|
||||
<template v-else-if="this.params.data[this.params.field].value_formatted">
|
||||
<i :class="this.params.data[this.params.field].icon || 'sn-icon sn-icon-calendar'"></i>
|
||||
{{ this.params.data[this.params.field].value_formatted }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ this.params.emptyPlaceholder }}
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import DateTimePicker from '../../date_time_picker.vue';
|
||||
|
||||
export default {
|
||||
name: 'dateRenderer',
|
||||
components: {
|
||||
DateTimePicker
|
||||
},
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
date: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.date = new Date(this.params.data[this.params.field].value?.replace(/([^!\s])-/g, '$1/'));
|
||||
},
|
||||
methods: {
|
||||
updateDate(value) {
|
||||
this.params.dtComponent.$emit(this.params.emitAction, value, this.params);
|
||||
this.date = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<template v-if="params.dtComponent.currentViewRender === 'table'">
|
||||
<div class="group relative flex items-center group-hover:marker text-xs h-full w-full leading-[unset]">
|
||||
<div class="flex gap-2 w-full items-center text-sm leading-[unset]">
|
||||
<div ref="descripitonBox" class="flex gap-2 w-full items-center text-sm leading-[unset]">
|
||||
<span class="cursor-pointer line-clamp-1 leading-[unset]"
|
||||
@click.stop="showDescriptionModal"
|
||||
v-html="params.data.sa_description">
|
||||
@click.stop="showDescriptionModal">
|
||||
{{ params.data.description }}
|
||||
</span>
|
||||
<span @click.stop="showDescriptionModal" class="text-sn-blue cursor-pointer shrink-0 inline-block text-sm">
|
||||
<span @click.stop="showDescriptionModal" class="text-sn-blue cursor-pointer shrink-0 inline-block text-sm leading-[unset]">
|
||||
{{ i18n.t('experiments.card.more') }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -14,13 +14,13 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<div class="group relative flex items-center group-hover:marker text-xs h-full w-full">
|
||||
<div class="flex gap-2 w-full items-end text-xs">
|
||||
<div ref="descripitonBox" class="flex gap-2 w-full items-end text-xs">
|
||||
<span v-if="shouldTruncateText"
|
||||
class="cursor-pointer grow line-clamp-2"
|
||||
@click.stop="showDescriptionModal"
|
||||
v-html="params.data.sa_description">
|
||||
@click.stop="showDescriptionModal">
|
||||
{{ params.data.description }}
|
||||
</span>
|
||||
<span v-else class="grow" v-html="params.data.sa_description"></span>
|
||||
<span v-else class="grow">{{ params.data.description }}</span>
|
||||
<span v-if="shouldTruncateText" @click.stop="showDescriptionModal" class="text-sn-blue cursor-pointer shrink-0 inline-block text-xs">
|
||||
{{ i18n.t('experiments.card.more') }}
|
||||
</span>
|
||||
|
|
@ -37,6 +37,11 @@ export default {
|
|||
required: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
window.renderElementSmartAnnotations(this.$refs.descripitonBox, 'span');
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
shouldTruncateText() {
|
||||
return this.params.data.description?.length > 60;
|
||||
|
|
@ -249,9 +249,7 @@ export default {
|
|||
const columns = this.columnDefs.map((column) => ({
|
||||
...column,
|
||||
minWidth: column.minWidth || 110,
|
||||
cellRendererParams: {
|
||||
dtComponent: this
|
||||
},
|
||||
cellRendererParams: { ...column.cellRendererParams, ...{ dtComponent: this } },
|
||||
pinned: (column.field === 'name' || column.field === 'name_hash' ? 'left' : null),
|
||||
comparator: () => null
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -22,10 +22,11 @@ module Lists
|
|||
tags_html
|
||||
comments
|
||||
due_date_formatted
|
||||
due_date_cell
|
||||
permissions
|
||||
default_public_user_role_id
|
||||
team
|
||||
)
|
||||
).freeze
|
||||
|
||||
def attributes(_options = {})
|
||||
ATTRIBUTES.index_with do |attribute|
|
||||
|
|
@ -67,13 +68,9 @@ module Lists
|
|||
provisioning_status: provisioning_status_my_module_url(object)
|
||||
}
|
||||
|
||||
if can_manage_project_users?(object.experiment.project)
|
||||
urls_list[:update_access] = access_permissions_my_module_path(object)
|
||||
end
|
||||
urls_list[:update_access] = access_permissions_my_module_path(object) if can_manage_project_users?(object.experiment.project)
|
||||
|
||||
if can_update_my_module_due_date?(object)
|
||||
urls_list[:update_due_date] = my_module_path(object, user, format: :json)
|
||||
end
|
||||
urls_list[:update_due_date] = my_module_path(object, user, format: :json) if can_update_my_module_due_date?(object)
|
||||
|
||||
urls_list
|
||||
end
|
||||
|
|
@ -86,6 +83,19 @@ module Lists
|
|||
I18n.l(object.due_date, format: :full_date) if object.due_date
|
||||
end
|
||||
|
||||
def due_date_cell
|
||||
{
|
||||
value: due_date,
|
||||
value_formatted: due_date_formatted,
|
||||
editable: can_update_my_module_due_date?(object),
|
||||
icon: (if object.is_one_day_prior? && !object.completed?
|
||||
'sn-icon sn-icon-alert-warning text-sn-alert-brittlebush'
|
||||
elsif object.is_overdue? && !object.completed?
|
||||
'sn-icon sn-icon-alert-warning text-sn-delete-red'
|
||||
end)
|
||||
}
|
||||
end
|
||||
|
||||
def due_date_status
|
||||
if (archived || object.completed?) && object.due_date
|
||||
return :ok
|
||||
|
|
|
|||
17
app/views/design_elements/_table.html.erb
Normal file
17
app/views/design_elements/_table.html.erb
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<div>
|
||||
<h1>Table</h1>
|
||||
<div id="table" class="h-[500px]">
|
||||
<data-table
|
||||
:column-defs="columnDefs"
|
||||
table-id="TestDataTable"
|
||||
:data-url="dataUrl"
|
||||
:reloading-table="reloadingTable"
|
||||
:toolbar-actions="toolbarActions"
|
||||
:actions-url="actionsUrl"
|
||||
scroll-mode="infinite"
|
||||
:filters="filters"
|
||||
></data-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= javascript_include_tag 'vue_design_system_table' %>
|
||||
|
|
@ -10,6 +10,8 @@
|
|||
end
|
||||
%>
|
||||
|
||||
<%= render partial: 'table' %>
|
||||
|
||||
<%= render partial: 'radio' %>
|
||||
|
||||
<%= render partial: 'inputs', locals: {icons_list: icons_list} %>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,8 @@ const entryList = {
|
|||
vue_form_show: './app/javascript/packs/vue/forms_show.js',
|
||||
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_inputs: './app/javascript/packs/vue/design_system/inputs.js',
|
||||
vue_design_system_table: './app/javascript/packs/vue/design_system/table.js'
|
||||
};
|
||||
|
||||
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue