mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-01-26 09:43:29 +08:00
Fix markup
This commit is contained in:
parent
7ffad5b659
commit
75f7437001
31 changed files with 1093 additions and 697 deletions
|
@ -267,6 +267,18 @@ class ExperimentsController < ApplicationController
|
|||
}
|
||||
end
|
||||
|
||||
def projects_to_clone
|
||||
projects = @experiment.project.team.projects.active
|
||||
.with_user_permission(current_user, ProjectPermissions::EXPERIMENTS_CREATE)
|
||||
.map { |p| [p.id, p.name] }
|
||||
render json: { data: projects }, status: :ok
|
||||
end
|
||||
|
||||
def projects_to_move
|
||||
projects = @experiment.movable_projects(current_user).map { |p| [p.id, p.name] }
|
||||
render json: { data: projects }, status: :ok
|
||||
end
|
||||
|
||||
# POST: clone_experiment(id)
|
||||
def clone
|
||||
project = current_team.projects.find(move_experiment_param)
|
||||
|
@ -279,11 +291,10 @@ class ExperimentsController < ApplicationController
|
|||
if service.succeed?
|
||||
flash[:success] = t('experiments.clone.success_flash',
|
||||
experiment: @experiment.name)
|
||||
redirect_to canvas_experiment_path(service.cloned_experiment)
|
||||
render json: { url: canvas_experiment_path(service.cloned_experiment) }
|
||||
else
|
||||
flash[:error] = t('experiments.clone.error_flash',
|
||||
experiment: @experiment.name)
|
||||
redirect_to experiments_path(project_id: @experiment.project)
|
||||
render json: { message: t('experiments.clone.error_flash',
|
||||
experiment: @experiment.name) }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -534,7 +545,7 @@ class ExperimentsController < ApplicationController
|
|||
actions:
|
||||
Toolbars::ExperimentsService.new(
|
||||
current_user,
|
||||
experiment_ids: params[:experiment_ids].split(',')
|
||||
experiment_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
|
|
@ -13,22 +13,58 @@
|
|||
:filters="filters"
|
||||
:viewRenders="viewRenders"
|
||||
@tableReloaded="reloadingTable = false"
|
||||
@archive="archive"
|
||||
@restore="restore"
|
||||
@showDescription="showDescription"
|
||||
@duplicate="duplicate"
|
||||
@move="move"
|
||||
>
|
||||
<template> </template>
|
||||
</DataTable>
|
||||
|
||||
<ConfirmationModal
|
||||
:title="i18n.t('experiments.index.archive_confirm_title')"
|
||||
:description="i18n.t('experiments.index.archive_confirm')"
|
||||
:confirmClass="'btn btn-primary'"
|
||||
:confirmText="i18n.t('general.archive')"
|
||||
ref="archiveModal"
|
||||
></ConfirmationModal>
|
||||
<DescriptionModal
|
||||
v-if="descriptionModalObject"
|
||||
:experiment="descriptionModalObject"
|
||||
@close="descriptionModalObject = null"/>
|
||||
<DuplicateModal
|
||||
v-if="duplicateModalObject"
|
||||
:experiment="duplicateModalObject"
|
||||
@close="duplicateModalObject = null"/>
|
||||
<MoveModal
|
||||
v-if="moveModalObject"
|
||||
:experiment="moveModalObject"
|
||||
@close="moveModalObject = null"
|
||||
@submit="updateTable"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
import DataTable from '../shared/datatable/table.vue';
|
||||
import DescriptionRenderer from './renderers/description.vue';
|
||||
import ConfirmationModal from '../shared/confirmation_modal.vue';
|
||||
import CompletedTasksRenderer from './renderers/completed_tasks.vue';
|
||||
import NameRenderer from './renderers/name.vue';
|
||||
import DescriptionModal from './modals/description.vue';
|
||||
import DuplicateModal from './modals/duplicate.vue';
|
||||
import MoveModal from './modals/move.vue';
|
||||
|
||||
export default {
|
||||
name: 'ExperimentsList',
|
||||
components: {
|
||||
DataTable,
|
||||
ConfirmationModal,
|
||||
DescriptionModal,
|
||||
DuplicateModal,
|
||||
MoveModal,
|
||||
},
|
||||
props: {
|
||||
dataSource: { type: String, required: true },
|
||||
|
@ -39,6 +75,9 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
moveModalObject: null,
|
||||
duplicateModalObject: null,
|
||||
descriptionModalObject: null,
|
||||
reloadingTable: false,
|
||||
};
|
||||
},
|
||||
|
@ -82,6 +121,7 @@ export default {
|
|||
headerName: this.i18n.t('experiments.card.completed_task'),
|
||||
cellRenderer: CompletedTasksRenderer,
|
||||
sortable: false,
|
||||
minWidth: 120,
|
||||
});
|
||||
columns.push({
|
||||
field: 'description',
|
||||
|
@ -130,6 +170,41 @@ export default {
|
|||
return filters;
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
methods: {
|
||||
updateTable() {
|
||||
this.moveModalObject = null;
|
||||
this.duplicateModalObject = null;
|
||||
this.descriptionModalObject = null;
|
||||
this.reloadingTable = true;
|
||||
},
|
||||
async archive(event, rows) {
|
||||
const ok = await this.$refs.archiveModal.show();
|
||||
if (ok) {
|
||||
axios.post(event.path, { experiment_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
}
|
||||
},
|
||||
restore(event, rows) {
|
||||
axios.post(event.path, { experiment_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
},
|
||||
showDescription(_e, experiment) {
|
||||
[this.descriptionModalObject] = experiment;
|
||||
},
|
||||
duplicate(_e, experiment) {
|
||||
[this.duplicateModalObject] = experiment;
|
||||
},
|
||||
move(_e, experiment) {
|
||||
[this.moveModalObject] = experiment;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
35
app/javascript/vue/experiments/modals/description.vue
Normal file
35
app/javascript/vue/experiments/modals/description.vue
Normal file
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<div ref="modal" class="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" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label" :title="experiment.name">
|
||||
{{ experiment.name }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ experiment.description }}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: 'DescriptionModal',
|
||||
props: {
|
||||
experiment: Object,
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
};
|
||||
</script>
|
68
app/javascript/vue/experiments/modals/duplicate.vue
Normal file
68
app/javascript/vue/experiments/modals/duplicate.vue
Normal file
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div ref="modal" class="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" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label" :title="experiment.name">
|
||||
{{ i18n.t("experiments.clone.modal_title", { experiment: experiment.name }) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<SelectDropdown :optionsUrl="experiment.urls.projects_to_clone"
|
||||
:value="targetProject"
|
||||
@change="changeProject" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" :disabled="!targetProject" @click="submit" type="submit">
|
||||
{{ i18n.t('experiments.clone.modal_submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: 'CloneModal',
|
||||
props: {
|
||||
experiment: Object,
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
targetProject: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
axios.post(this.experiment.urls.clone, {
|
||||
experiment: {
|
||||
project_id: this.targetProject,
|
||||
},
|
||||
}).then((response) => {
|
||||
this.$emit('update');
|
||||
window.location.replace(response.data.url);
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.message, 'danger');
|
||||
});
|
||||
},
|
||||
changeProject(project) {
|
||||
this.targetProject = project;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
69
app/javascript/vue/experiments/modals/move.vue
Normal file
69
app/javascript/vue/experiments/modals/move.vue
Normal file
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<div ref="modal" class="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" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label" :title="experiment.name">
|
||||
{{ i18n.t("experiments.move.modal_title", { experiment: experiment.name }) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><small>{{ i18n.t("experiments.move.notice") }}</small></p>
|
||||
<SelectDropdown :optionsUrl="experiment.urls.projects_to_move"
|
||||
:value="targetProject"
|
||||
@change="changeProject" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" :disabled="!targetProject" @click="submit" type="submit">
|
||||
{{ i18n.t('experiments.clone.modal_submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: 'MoveModal',
|
||||
props: {
|
||||
experiment: Object,
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
targetProject: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
axios.post(this.experiment.urls.move, {
|
||||
experiment: {
|
||||
project_id: this.targetProject,
|
||||
},
|
||||
}).then((response) => {
|
||||
this.$emit('submit');
|
||||
window.location.replace(response.data.url);
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.message, 'danger');
|
||||
});
|
||||
},
|
||||
changeProject(project) {
|
||||
this.targetProject = project;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -6,7 +6,7 @@
|
|||
all: params.data.total_tasks
|
||||
}) }}
|
||||
<div class="py-1">
|
||||
<div class="w-48 h-1 bg-sn-light-grey">
|
||||
<div class="w-24 h-1 bg-sn-light-grey">
|
||||
<div class="h-full bg-sn-blue" :style="{
|
||||
width: params.data.completed_tasks / params.data.total_tasks * 100 + '%'
|
||||
}"></div>
|
||||
|
|
|
@ -25,7 +25,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
showMore() {
|
||||
|
||||
this.params.dtComponent.$emit('showDescription', null, [this.params.data]);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div ref="dropdown" class="insert-field-dropdown dropdown">
|
||||
<a class="open-dropdown-button collapsed" role="button" data-toggle="dropdown" id="fieldsContainer" aria-expanded="false">
|
||||
<a class="open-dropdown-button collapsed" role="button" data-toggle="dropdown"
|
||||
id="fieldsContainer" aria-expanded="false">
|
||||
{{ i18n.t('label_templates.show.insert_dropdown.button') }}
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</a>
|
||||
|
@ -74,98 +75,97 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import LogoInsertModal from './components/logo_insert_modal.vue'
|
||||
import LogoInsertModal from './components/logo_insert_modal.vue';
|
||||
|
||||
export default {
|
||||
name: 'InsertFieldDropdown',
|
||||
props: {
|
||||
labelTemplate: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
export default {
|
||||
name: 'InsertFieldDropdown',
|
||||
props: {
|
||||
labelTemplate: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fields: {
|
||||
default: [],
|
||||
common: [],
|
||||
repositories: []
|
||||
},
|
||||
openLogoModal: false,
|
||||
logoDimension: null,
|
||||
searchValue: ''
|
||||
}
|
||||
},
|
||||
components: {LogoInsertModal},
|
||||
computed: {
|
||||
tooltipTemplate() {
|
||||
return `<div class="tooltip" role="tooltip">
|
||||
<div class="tooltip-arrow"></div>
|
||||
<div class="tooltip-body">
|
||||
<div class="tooltip-inner"></div>
|
||||
</div>
|
||||
</div>`
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fields: {
|
||||
default: [],
|
||||
common: [],
|
||||
repositories: [],
|
||||
},
|
||||
filteredFields() {
|
||||
this.$nextTick(() => {
|
||||
$('.tooltip').remove();
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
|
||||
if (this.searchValue.length == 0) {
|
||||
return this.fields;
|
||||
} else {
|
||||
return {
|
||||
default: this.filterArray(this.fields.default, 'key'),
|
||||
common: this.filterArray(this.fields.common, 'key'),
|
||||
repositories: this.filterArray(this.fields.repositories, 'repository_name').map((repo) => {
|
||||
return { ...repo, tags: this.filterArray(repo.tags, 'key') };
|
||||
})
|
||||
};
|
||||
}
|
||||
},
|
||||
noResults() {
|
||||
return this.filteredFields.default.concat(this.filteredFields.common, this.filteredFields.repositories).length === 0;
|
||||
}
|
||||
openLogoModal: false,
|
||||
logoDimension: null,
|
||||
searchValue: '',
|
||||
};
|
||||
},
|
||||
components: { LogoInsertModal },
|
||||
computed: {
|
||||
tooltipTemplate() {
|
||||
return `<div class="tooltip" role="tooltip">
|
||||
<div class="tooltip-arrow"></div>
|
||||
<div class="tooltip-body">
|
||||
<div class="tooltip-inner"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
mounted() {
|
||||
$.get(this.labelTemplate.attributes.urls.fields, (result) => {
|
||||
result.default.map((value) => {
|
||||
value.key = this.i18n.t(`label_templates.default_columns.${value.key}`)
|
||||
return value;
|
||||
});
|
||||
|
||||
this.fields = result;
|
||||
this.$nextTick(() => {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
});
|
||||
filteredFields() {
|
||||
this.$nextTick(() => {
|
||||
$(this.$refs.dropdown).on('show.bs.dropdown', () => {
|
||||
this.$nextTick(() => {
|
||||
$('.insert-field-dropdown')[1].focus()
|
||||
});
|
||||
this.searchValue = '';
|
||||
});
|
||||
$('.tooltip').remove();
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
insertTag(field) {
|
||||
if (field.id == 'logo') {
|
||||
this.logoDimension = field.dimension
|
||||
this.openLogoModal = true
|
||||
return
|
||||
}
|
||||
this.$emit('insertTag', field.tag)
|
||||
},
|
||||
filterArray(array, key) {
|
||||
return array.filter(field => {
|
||||
return (
|
||||
field[key].toLowerCase().indexOf(this.searchValue.toLowerCase()) !== -1 ||
|
||||
field.tags
|
||||
);
|
||||
});
|
||||
|
||||
if (this.searchValue.length === 0) {
|
||||
return this.fields;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
default: this.filterArray(this.fields.default, 'key'),
|
||||
common: this.filterArray(this.fields.common, 'key'),
|
||||
repositories: this.filterArray(this.fields.repositories, 'repository_name').map((repo) => (
|
||||
{ ...repo, tags: this.filterArray(repo.tags, 'key') }
|
||||
)),
|
||||
};
|
||||
},
|
||||
noResults() {
|
||||
const defaultField = this.filteredFields.default;
|
||||
return defaultField.concat(this.filteredFields.common, this.filteredFields.repositories).length === 0;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
$.get(this.labelTemplate.attributes.urls.fields, (result) => {
|
||||
result.default.map((value) => {
|
||||
const newValue = value;
|
||||
newValue.key = this.i18n.t(`label_templates.default_columns.${value.key}`);
|
||||
return newValue;
|
||||
});
|
||||
|
||||
this.fields = result;
|
||||
this.$nextTick(() => {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
});
|
||||
this.$nextTick(() => {
|
||||
$(this.$refs.dropdown).on('show.bs.dropdown', () => {
|
||||
this.$nextTick(() => {
|
||||
$('.insert-field-dropdown')[1].focus();
|
||||
});
|
||||
this.searchValue = '';
|
||||
});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
insertTag(field) {
|
||||
if (field.id === 'logo') {
|
||||
this.logoDimension = field.dimension;
|
||||
this.openLogoModal = true;
|
||||
return;
|
||||
}
|
||||
this.$emit('insertTag', field.tag);
|
||||
},
|
||||
filterArray(array, key) {
|
||||
return array.filter((field) => (
|
||||
field[key].toLowerCase().indexOf(this.searchValue.toLowerCase()) !== -1
|
||||
|| field.tags
|
||||
));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -24,51 +24,83 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
|
||||
import DataTable from '../shared/datatable/table.vue'
|
||||
import DeleteModal from '../shared/confirmation_modal.vue'
|
||||
import DataTable from '../shared/datatable/table.vue';
|
||||
import DeleteModal from '../shared/confirmation_modal.vue';
|
||||
|
||||
export default {
|
||||
name: 'LabelTemplatesTable',
|
||||
components: {
|
||||
DataTable,
|
||||
DeleteModal
|
||||
DeleteModal,
|
||||
},
|
||||
props: {
|
||||
dataSource: {
|
||||
type: String,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
actionsUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
createUrl: {
|
||||
type: String,
|
||||
},
|
||||
syncFluicsUrl: {
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
reloadingTable: false,
|
||||
columnDefs: [ { field: "default", headerName: '', width: 80, minWidth: 80,
|
||||
cellRenderer: this.defaultRenderer, sortable: true, headerComponentParams: { html: '<i class="fas fa-thumbtack"></i>' } },
|
||||
{ field: "name", headerName: this.i18n.t('label_templates.index.thead_name'), cellRenderer: this.labelNameRenderer, sortable: true},
|
||||
{ field: "format", headerName: this.i18n.t('label_templates.index.format'), sortable: true },
|
||||
{ field: "description", headerName: this.i18n.t('label_templates.index.description'), sortable: true },
|
||||
{ field: "modified_by", headerName: this.i18n.t('label_templates.index.updated_by'), sortable: true },
|
||||
{ field: "updated_at", headerName: this.i18n.t('label_templates.index.updated_at'), sortable: true },
|
||||
{ field: "created_by", headerName: this.i18n.t('label_templates.index.created_by'), sortable: true },
|
||||
{ field: "created_at", headerName: this.i18n.t('label_templates.index.created_at'), sortable: true }
|
||||
]
|
||||
}
|
||||
columnDefs: [
|
||||
{
|
||||
field: 'default',
|
||||
headerName: '',
|
||||
width: 80,
|
||||
minWidth: 80,
|
||||
cellRenderer: this.defaultRenderer,
|
||||
sortable: true,
|
||||
headerComponentParams: { html: '<i class="fas fa-thumbtack"></i>' },
|
||||
}, {
|
||||
field: 'name',
|
||||
headerName: this.i18n.t('label_templates.index.thead_name'),
|
||||
cellRenderer: this.labelNameRenderer,
|
||||
sortable: true,
|
||||
}, {
|
||||
field: 'format',
|
||||
headerName: this.i18n.t('label_templates.index.format'),
|
||||
sortable: true,
|
||||
}, {
|
||||
field: 'description',
|
||||
headerName: this.i18n.t('label_templates.index.description'),
|
||||
sortable: true,
|
||||
}, {
|
||||
field: 'modified_by',
|
||||
headerName: this.i18n.t('label_templates.index.updated_by'),
|
||||
sortable: true,
|
||||
}, {
|
||||
field: 'updated_at',
|
||||
headerName: this.i18n.t('label_templates.index.updated_at'),
|
||||
sortable: true,
|
||||
}, {
|
||||
field: 'created_by',
|
||||
headerName: this.i18n.t('label_templates.index.created_by'),
|
||||
sortable: true,
|
||||
}, {
|
||||
field: 'created_at',
|
||||
headerName: this.i18n.t('label_templates.index.created_at'),
|
||||
sortable: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
toolbarActions() {
|
||||
let left = []
|
||||
const left = [];
|
||||
if (this.createUrl) {
|
||||
left.push({
|
||||
name: 'create',
|
||||
|
@ -76,8 +108,8 @@ export default {
|
|||
label: this.i18n.t('label_templates.index.toolbar.new'),
|
||||
type: 'emit',
|
||||
path: this.createUrl,
|
||||
buttonStyle: 'btn btn-primary'
|
||||
})
|
||||
buttonStyle: 'btn btn-primary',
|
||||
});
|
||||
}
|
||||
if (this.syncFluicsUrl) {
|
||||
left.push({
|
||||
|
@ -86,30 +118,30 @@ export default {
|
|||
label: this.i18n.t('label_templates.index.toolbar.update_fluics_labels'),
|
||||
type: 'emit',
|
||||
path: this.syncFluicsUrl,
|
||||
buttonStyle: 'btn btn-light'
|
||||
})
|
||||
buttonStyle: 'btn btn-light',
|
||||
});
|
||||
}
|
||||
return {
|
||||
left: left,
|
||||
right: []
|
||||
}
|
||||
}
|
||||
left,
|
||||
right: [],
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
labelNameRenderer(params) {
|
||||
let editUrl = params.data.urls.show;
|
||||
const editUrl = params.data.urls.show;
|
||||
return `<a href="${editUrl}">
|
||||
${params.data.icon_url}
|
||||
${params.data.name}
|
||||
</a>`
|
||||
</a>`;
|
||||
},
|
||||
defaultRenderer(params) {
|
||||
let defaultSelected = params.data.default;
|
||||
const defaultSelected = params.data.default;
|
||||
return defaultSelected ? '<i class="fas fa-thumbtack"></i>' : '';
|
||||
},
|
||||
setDefault(action) {
|
||||
axios.post(action.path).then((response) => {
|
||||
this.reloadingTable = true
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
|
@ -117,7 +149,7 @@ export default {
|
|||
},
|
||||
duplicate(action, rows) {
|
||||
axios.post(action.path, { selected_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
this.reloadingTable = true
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
|
@ -126,28 +158,28 @@ export default {
|
|||
createTemplate(action) {
|
||||
axios.post(action.path).then((response) => {
|
||||
window.location.href = response.data.redirect_url;
|
||||
})
|
||||
});
|
||||
},
|
||||
syncFluicsLabels(action) {
|
||||
axios.post(action.path).then((response) => {
|
||||
this.reloadingTable = true
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
},
|
||||
async deleteTemplates(action, rows) {
|
||||
const ok = await this.$refs.deleteModal.show()
|
||||
const ok = await this.$refs.deleteModal.show();
|
||||
if (ok) {
|
||||
axios.delete(action.path, { data: { selected_ids: rows.map((row) => row.id) } }).then((response) => {
|
||||
this.reloadingTable = true
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
<div>{{ params.code }}</div>
|
||||
<RowMenuRenderer :params="{data: params, dtComponent: dtComponent}" class="ml-auto"/>
|
||||
</div>
|
||||
<a :href="params.urls.show" :class="{'pointer-events-none text-sn-grey': !params.urls.show}" class="font-bold mb-4 text-sn-black hover:no-underline hover:text-sn-black">
|
||||
<a :href="params.urls.show"
|
||||
:class="{'pointer-events-none text-sn-grey': !params.urls.show}"
|
||||
class="font-bold mb-4 text-sn-black hover:no-underline hover:text-sn-black">
|
||||
{{ params.name }}
|
||||
</a>
|
||||
<div class="grid gap-2 grid-cols-[80px_auto] mt-auto">
|
||||
|
@ -24,8 +26,9 @@
|
|||
<span class="font-bold">{{ params.archived_on }}</span>
|
||||
</template>
|
||||
<span class="text-sn-grey">{{ i18n.t('projects.index.card.visibility') }}</span>
|
||||
<span class="font-bold">{{ params.hidden ? i18n.t('projects.index.hidden') : i18n.t('projects.index.visible') }}</span>
|
||||
|
||||
<span class="font-bold">
|
||||
{{ params.hidden ? i18n.t('projects.index.hidden') : i18n.t('projects.index.visible') }}
|
||||
</span>
|
||||
<span class="text-sn-grey">{{ i18n.t('projects.index.card.users') }}</span>
|
||||
<UsersRenderer :params="{data: params, value: params.users, dtComponent: dtComponent}" class="-mt-2.5" />
|
||||
</div>
|
||||
|
@ -45,7 +48,9 @@
|
|||
<div class="flex-grow flex items-center justify-center min-h-[6rem] text-sn-blue">
|
||||
<i class="sn-icon sn-icon-folder"></i>
|
||||
</div>
|
||||
<a :href="params.urls.show" class="flex items-center justify-center gap-1 font-bold mb-2 text-sn-black hover:no-underline hover:text-sn-black">
|
||||
<a :href="params.urls.show"
|
||||
class="flex items-center justify-center gap-1 font-bold mb-2
|
||||
text-sn-black hover:no-underline hover:text-sn-black">
|
||||
<i class="sn-icon mini sn-icon-mini-folder-left"></i>
|
||||
{{ params.name }}
|
||||
</a>
|
||||
|
@ -57,20 +62,20 @@
|
|||
|
||||
<script>
|
||||
|
||||
import RowMenuRenderer from '../shared/datatable/row_menu_renderer.vue'
|
||||
import UsersRenderer from './renderers/users.vue'
|
||||
import CardSelectorMixin from '../shared/datatable/mixins/card_selector.js'
|
||||
import RowMenuRenderer from '../shared/datatable/row_menu_renderer.vue';
|
||||
import UsersRenderer from './renderers/users.vue';
|
||||
import CardSelectorMixin from '../shared/datatable/mixins/card_selector.js';
|
||||
|
||||
export default {
|
||||
name: "ProjectCard",
|
||||
name: 'ProjectCard',
|
||||
props: {
|
||||
params: Object,
|
||||
dtComponent: Object
|
||||
dtComponent: Object,
|
||||
},
|
||||
components: {
|
||||
RowMenuRenderer,
|
||||
UsersRenderer
|
||||
UsersRenderer,
|
||||
},
|
||||
mixins: [CardSelectorMixin]
|
||||
}
|
||||
mixins: [CardSelectorMixin],
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
<ProjectCard :params="data.params" :dtComponent="data.dtComponent" ></ProjectCard>
|
||||
</template>
|
||||
</DataTable>
|
||||
<a href="#" ref="commentButton" class="open-comments-sidebar hidden" data-turbolinks="false" data-object-type="Project" data-object-id=""></a>
|
||||
<a href="#" ref="commentButton" class="open-comments-sidebar hidden"
|
||||
data-turbolinks="false" data-object-type="Project" data-object-id=""></a>
|
||||
<ConfirmationModal
|
||||
:title="i18n.t('projects.index.archive_confirm_title')"
|
||||
:description="i18n.t('projects.index.archive_confirm')"
|
||||
|
@ -49,27 +50,38 @@
|
|||
:confirmText="i18n.t('projects.export_projects.export_button')"
|
||||
ref="exportModal"
|
||||
></ConfirmationModal>
|
||||
<EditProjectModal v-if="editProject" :userRolesUrl="userRolesUrl" :project="editProject" @close="editProject = null" @update="updateTable" />
|
||||
<EditFolderModal v-if="editFolder" :folder="editFolder" @close="editFolder = null" @update="updateTable" />
|
||||
<NewProjectModal v-if="newProject" :createUrl="createUrl" :currentFolderId="currentFolderId" :userRolesUrl="userRolesUrl" @close="newProject = false" @create="updateTable" />
|
||||
<NewFolderModal v-if="newFolder" :createFolderUrl="createFolderUrl" :currentFolderId="currentFolderId" :viewMode="currentViewMode" @close="newFolder = false" @create="updateTable" />
|
||||
<MoveModal v-if="objectsToMove" :moveToUrl="moveToUrl" :selectedObjects="objectsToMove" :foldersTreeUrl="foldersTreeUrl" @close="objectsToMove = null" @move="updateTable" />
|
||||
<AccessModal v-if="accessModalParams" :params="accessModalParams" @close="accessModalParams = null" @refresh="this.reloadingTable = true" />
|
||||
<EditProjectModal v-if="editProject" :userRolesUrl="userRolesUrl"
|
||||
:project="editProject" @close="editProject = null" @update="updateTable" />
|
||||
<EditFolderModal v-if="editFolder" :folder="editFolder"
|
||||
@close="editFolder = null" @update="updateTable" />
|
||||
<NewProjectModal v-if="newProject" :createUrl="createUrl"
|
||||
:currentFolderId="currentFolderId" :userRolesUrl="userRolesUrl"
|
||||
@close="newProject = false" @create="updateTable" />
|
||||
<NewFolderModal v-if="newFolder" :createFolderUrl="createFolderUrl"
|
||||
:currentFolderId="currentFolderId" :viewMode="currentViewMode"
|
||||
@close="newFolder = false" @create="updateTable" />
|
||||
<MoveModal v-if="objectsToMove" :moveToUrl="moveToUrl"
|
||||
:selectedObjects="objectsToMove" :foldersTreeUrl="foldersTreeUrl"
|
||||
@close="objectsToMove = null" @move="updateTable" />
|
||||
<AccessModal v-if="accessModalParams" :params="accessModalParams"
|
||||
@close="accessModalParams = null" @refresh="this.reloadingTable = true" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
|
||||
import DataTable from '../shared/datatable/table.vue'
|
||||
import UsersRenderer from './renderers/users.vue'
|
||||
import ProjectCard from './card.vue'
|
||||
import ConfirmationModal from '../shared/confirmation_modal.vue'
|
||||
import EditProjectModal from './modals/edit.vue'
|
||||
import EditFolderModal from './modals/edit_folder.vue'
|
||||
import NewProjectModal from './modals/new.vue'
|
||||
import NewFolderModal from './modals/new_folder.vue'
|
||||
import MoveModal from './modals/move.vue'
|
||||
import AccessModal from '../shared/access_modal/modal.vue'
|
||||
import DataTable from '../shared/datatable/table.vue';
|
||||
import UsersRenderer from './renderers/users.vue';
|
||||
import ProjectCard from './card.vue';
|
||||
import ConfirmationModal from '../shared/confirmation_modal.vue';
|
||||
import EditProjectModal from './modals/edit.vue';
|
||||
import EditFolderModal from './modals/edit_folder.vue';
|
||||
import NewProjectModal from './modals/new.vue';
|
||||
import NewFolderModal from './modals/new_folder.vue';
|
||||
import MoveModal from './modals/move.vue';
|
||||
import AccessModal from '../shared/access_modal/modal.vue';
|
||||
|
||||
export default {
|
||||
name: 'ProjectsList',
|
||||
|
@ -83,7 +95,7 @@ export default {
|
|||
NewProjectModal,
|
||||
NewFolderModal,
|
||||
MoveModal,
|
||||
AccessModal
|
||||
AccessModal,
|
||||
},
|
||||
props: {
|
||||
dataSource: { type: String, required: true },
|
||||
|
@ -111,23 +123,49 @@ export default {
|
|||
folderDeleteDescription: '',
|
||||
exportDescription: '',
|
||||
columnDefs: [
|
||||
{ field: "name", flex: 1, headerName: this.i18n.t('projects.index.card.name'), sortable: true, cellRenderer: this.nameRenderer },
|
||||
{ field: "code", headerName: this.i18n.t('projects.index.card.id'), sortable: true },
|
||||
{ field: "created_at", headerName: this.i18n.t('projects.index.card.start_date'), sortable: true },
|
||||
{ field: "hidden", headerName: this.i18n.t('projects.index.card.visibility'), cellRenderer: this.visibiltyRenderer, sortable: false },
|
||||
{ field: "users", headerName: this.i18n.t('projects.index.card.users'), cellRenderer: 'UsersRenderer', sortable: false, minWidth: 210, notSelectable: true }
|
||||
]
|
||||
}
|
||||
{
|
||||
field: 'name',
|
||||
flex: 1,
|
||||
headerName: this.i18n.t('projects.index.card.name'),
|
||||
sortable: true,
|
||||
cellRenderer: this.nameRenderer,
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
headerName: this.i18n.t('projects.index.card.id'),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
headerName: this.i18n.t('projects.index.card.start_date'),
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
field: 'hidden',
|
||||
headerName: this.i18n.t('projects.index.card.visibility'),
|
||||
cellRenderer: this.visibiltyRenderer,
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
field: 'users',
|
||||
headerName: this.i18n.t('projects.index.card.users'),
|
||||
cellRenderer: 'UsersRenderer',
|
||||
sortable: false,
|
||||
minWidth: 210,
|
||||
notSelectable: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
viewRenders() {
|
||||
return [
|
||||
{type: 'table'},
|
||||
{type: 'cards'}
|
||||
]
|
||||
{ type: 'table' },
|
||||
{ type: 'cards' },
|
||||
];
|
||||
},
|
||||
toolbarActions() {
|
||||
let left = []
|
||||
const left = [];
|
||||
if (this.createUrl && this.currentViewMode !== 'archived') {
|
||||
left.push({
|
||||
name: 'create',
|
||||
|
@ -135,8 +173,8 @@ export default {
|
|||
label: this.i18n.t('projects.index.new'),
|
||||
type: 'emit',
|
||||
path: this.createUrl,
|
||||
buttonStyle: 'btn btn-primary'
|
||||
})
|
||||
buttonStyle: 'btn btn-primary',
|
||||
});
|
||||
}
|
||||
if (this.createFolderUrl) {
|
||||
left.push({
|
||||
|
@ -146,30 +184,32 @@ export default {
|
|||
type: 'emit',
|
||||
path: this.createFolderUrl,
|
||||
buttonStyle: 'btn btn-light',
|
||||
})
|
||||
});
|
||||
}
|
||||
return {
|
||||
left: left,
|
||||
right: []
|
||||
}
|
||||
left,
|
||||
right: [],
|
||||
};
|
||||
},
|
||||
filters() {
|
||||
let filters = [{
|
||||
key: 'query',
|
||||
type: 'Text'
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
type: 'DateRange',
|
||||
label: this.i18n.t("filters_modal.created_on.label"),
|
||||
}]
|
||||
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"),
|
||||
})
|
||||
label: this.i18n.t('filters_modal.archived_on.label'),
|
||||
});
|
||||
}
|
||||
|
||||
filters.push({
|
||||
|
@ -178,35 +218,37 @@ export default {
|
|||
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"),
|
||||
})
|
||||
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"),
|
||||
})
|
||||
label: this.i18n.t('projects.index.filters_modal.folders.label'),
|
||||
});
|
||||
|
||||
return filters
|
||||
}
|
||||
return filters;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
usersFilterRenderer(option) {
|
||||
return `<div class="flex items-center gap-2">
|
||||
<img src="${option[2].avatar_url}" class="rounded-full w-6 h-6" />
|
||||
<span>${option[1]}</span>
|
||||
</div>`
|
||||
</div>`;
|
||||
},
|
||||
nameRenderer(params) {
|
||||
let showUrl = params.data.urls.show;
|
||||
return `<a href="${showUrl}" class="flex items-center gap-1 hover:no-underline ${!showUrl ? 'pointer-events-none text-sn-grey' : ''}">
|
||||
const showUrl = params.data.urls.show;
|
||||
return `<a href="${showUrl}"
|
||||
class="flex items-center gap-1 hover:no-underline
|
||||
${!showUrl ? 'pointer-events-none text-sn-grey' : ''}">
|
||||
${params.data.folder ? '<i class="sn-icon mini sn-icon-mini-folder-left"></i>' : ''}
|
||||
${params.data.name}
|
||||
</a>`
|
||||
</a>`;
|
||||
},
|
||||
visibiltyRenderer(params) {
|
||||
if (params.data.type !== 'projects') return ''
|
||||
if (params.data.type !== 'projects') return '';
|
||||
return params.data.hidden ? this.i18n.t('projects.index.hidden') : this.i18n.t('projects.index.visible');
|
||||
},
|
||||
openComments(_params, rows) {
|
||||
|
@ -217,13 +259,13 @@ export default {
|
|||
this.accessModalParams = {
|
||||
object: rows[0],
|
||||
roles_path: this.userRolesUrl,
|
||||
}
|
||||
};
|
||||
},
|
||||
async archive(event, rows) {
|
||||
const ok = await this.$refs.archiveModal.show()
|
||||
const ok = await this.$refs.archiveModal.show();
|
||||
if (ok) {
|
||||
axios.post(event.path, { project_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
this.reloadingTable = true
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
|
@ -232,7 +274,7 @@ export default {
|
|||
},
|
||||
restore(event, rows) {
|
||||
axios.post(event.path, { project_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
this.reloadingTable = true
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
|
@ -240,10 +282,10 @@ export default {
|
|||
},
|
||||
edit(event, rows) {
|
||||
if (rows[0].folder) {
|
||||
this.editFolder = rows[0]
|
||||
return
|
||||
[this.editFolder] = rows;
|
||||
return;
|
||||
}
|
||||
this.editProject = rows[0]
|
||||
[this.editProject] = rows;
|
||||
},
|
||||
create() {
|
||||
this.newProject = true;
|
||||
|
@ -260,14 +302,14 @@ export default {
|
|||
this.reloadingTable = true;
|
||||
},
|
||||
async deleteFolder(event, rows) {
|
||||
const description =`
|
||||
<p>${this.i18n.t('projects.index.modal_delete_folders.description_1_html', {number: rows.length}) }</p>
|
||||
<p>${this.i18n.t('projects.index.modal_delete_folders.description_2')}</p>`
|
||||
const description = `
|
||||
<p>${this.i18n.t('projects.index.modal_delete_folders.description_1_html', { number: rows.length })}</p>
|
||||
<p>${this.i18n.t('projects.index.modal_delete_folders.description_2')}</p>`;
|
||||
this.folderDeleteDescription = description;
|
||||
const ok = await this.$refs.deleteFolderModal.show();
|
||||
if (ok) {
|
||||
axios.post(event.path, { project_folder_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
this.reloadingTable = true
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
|
@ -276,13 +318,13 @@ export default {
|
|||
},
|
||||
async exportProjects(event, rows) {
|
||||
this.exportDescription = event.message;
|
||||
const ok = await this.$refs.exportModal.show()
|
||||
const ok = await this.$refs.exportModal.show();
|
||||
if (ok) {
|
||||
axios.post(event.path, {
|
||||
project_ids: rows.filter((row) => !row.folder).map((row) => row.id),
|
||||
project_folder_ids: rows.filter((row) => row.folder).map((row) => row.id),
|
||||
}).then((response) => {
|
||||
this.reloadingTable = true
|
||||
this.reloadingTable = true;
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
|
@ -291,8 +333,8 @@ export default {
|
|||
},
|
||||
move(event, rows) {
|
||||
this.objectsToMove = rows;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label" :title="project.name">
|
||||
{{ i18n.t('projects.index.modal_edit_project.modal_title', {project: project.name}) }}
|
||||
</h4>
|
||||
|
@ -12,7 +14,9 @@
|
|||
<div class="mb-6">
|
||||
<label class="sci-label">{{ i18n.t("projects.index.modal_new_project.name") }}</label>
|
||||
<div class="sci-input-container-v2" :class="{'error': error}" :data-error="error">
|
||||
<input type="text" v-model="name" class="sci-input-field" autofocus="true" :placeholder="i18n.t('projects.index.modal_new_project.name_placeholder')" />
|
||||
<input type="text" v-model="name" class="sci-input-field"
|
||||
autofocus="true"
|
||||
:placeholder="i18n.t('projects.index.modal_new_project.name_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 text-xs items-center">
|
||||
|
@ -29,7 +33,9 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">{{ i18n.t('projects.index.modal_edit_project.submit') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">
|
||||
{{ i18n.t('projects.index.modal_edit_project.submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -38,17 +44,17 @@
|
|||
|
||||
<script>
|
||||
|
||||
import SelectDropdown from "../../shared/select_dropdown.vue";
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modal_mixin from "../../shared/modal_mixin";
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: "EditProjectModal",
|
||||
name: 'EditProjectModal',
|
||||
props: {
|
||||
project: Object,
|
||||
userRolesUrl: String,
|
||||
},
|
||||
mixins: [modal_mixin],
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown,
|
||||
},
|
||||
|
@ -67,17 +73,17 @@ export default {
|
|||
name: this.name,
|
||||
visibility: (this.visible ? 'visible' : 'hidden'),
|
||||
default_public_user_role_id: this.defaultRole,
|
||||
}
|
||||
},
|
||||
}).then(() => {
|
||||
this.error = null;
|
||||
this.$emit('update');
|
||||
}).catch((error) => {
|
||||
this.error = error.response.data.errors.name;
|
||||
})
|
||||
});
|
||||
},
|
||||
changeRole(role) {
|
||||
this.defaultRole = role;
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label" :title="folder.name">
|
||||
{{ i18n.t('projects.index.modal_edit_folder.title', {folder: folder.name}) }}
|
||||
</h4>
|
||||
|
@ -12,13 +14,17 @@
|
|||
<div class="mb-6">
|
||||
<label class="sci-label">{{ i18n.t("projects.index.modal_edit_folder.folder_name_field") }}</label>
|
||||
<div class="sci-input-container-v2" :class="{'error': error}" :data-error="error">
|
||||
<input type="text" v-model="name" class="sci-input-field" autofocus="true" :placeholder="i18n.t('projects.index.modal_new_project.name_placeholder')" />
|
||||
<input type="text" v-model="name"
|
||||
class="sci-input-field" autofocus="true"
|
||||
:placeholder="i18n.t('projects.index.modal_new_project.name_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">{{ i18n.t('projects.index.modal_edit_folder.submit') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">
|
||||
{{ i18n.t('projects.index.modal_edit_folder.submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -27,16 +33,16 @@
|
|||
|
||||
<script>
|
||||
|
||||
import SelectDropdown from "../../shared/select_dropdown.vue";
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modal_mixin from "../../shared/modal_mixin";
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: "EditFolderModal",
|
||||
name: 'EditFolderModal',
|
||||
props: {
|
||||
folder: Object,
|
||||
},
|
||||
mixins: [modal_mixin],
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown,
|
||||
},
|
||||
|
@ -51,14 +57,14 @@ export default {
|
|||
axios.put(this.folder.urls.update, {
|
||||
project_folder: {
|
||||
name: this.name,
|
||||
}
|
||||
},
|
||||
}).then(() => {
|
||||
this.error = null;
|
||||
this.$emit('update');
|
||||
}).catch((error) => {
|
||||
this.error = error.response.data.errors.name;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label">
|
||||
{{ this.title }}
|
||||
</h4>
|
||||
|
@ -12,7 +14,11 @@
|
|||
<div class="mb-4">{{ this.description }}</div>
|
||||
<div class="mb-4">
|
||||
<div class="sci-input-container-v2 left-icon">
|
||||
<input type="text" v-model="query" class="sci-input-field" autofocus="true" :placeholder="i18n.t('projects.index.modal_move_folder.find_folder')" />
|
||||
<input type="text"
|
||||
v-model="query"
|
||||
class="sci-input-field"
|
||||
autofocus="true"
|
||||
:placeholder="i18n.t('projects.index.modal_move_folder.find_folder')" />
|
||||
<i class="sn-icon sn-icon-search"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -28,7 +34,9 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">{{ i18n.t('projects.index.modal_move_folder.submit') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">
|
||||
{{ i18n.t('projects.index.modal_move_folder.submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -36,19 +44,20 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modal_mixin from "../../shared/modal_mixin";
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
import MoveTree from './move_tree.vue';
|
||||
|
||||
export default {
|
||||
name: "NewProjectModal",
|
||||
name: 'NewProjectModal',
|
||||
props: {
|
||||
selectedObjects: Array,
|
||||
foldersTreeUrl: String,
|
||||
moveToUrl: String,
|
||||
},
|
||||
mixins: [modal_mixin],
|
||||
mixins: [modalMixin],
|
||||
data() {
|
||||
return {
|
||||
selectedFolderId: null,
|
||||
|
@ -57,7 +66,7 @@ export default {
|
|||
};
|
||||
},
|
||||
components: {
|
||||
MoveTree
|
||||
MoveTree,
|
||||
},
|
||||
mounted() {
|
||||
axios.get(this.foldersTreeUrl).then((response) => {
|
||||
|
@ -66,39 +75,38 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
itemsName() {
|
||||
return this.i18n.t('projects.index.modal_move_folder.items.' + this.itemsType);
|
||||
return this.i18n.t(`projects.index.modal_move_folder.items.${this.itemsType}`);
|
||||
},
|
||||
title() {
|
||||
return this.i18n.t('projects.index.modal_move_folder.title', {items: this.itemsName} );
|
||||
return this.i18n.t('projects.index.modal_move_folder.title', { items: this.itemsName });
|
||||
},
|
||||
description() {
|
||||
return this.i18n.t('projects.index.modal_move_folder.description', {items: this.itemsName} );
|
||||
return this.i18n.t('projects.index.modal_move_folder.description', { items: this.itemsName });
|
||||
},
|
||||
itemsType() {
|
||||
const allTypes = this.selectedObjects.map(obj => obj.type);
|
||||
const allTypes = this.selectedObjects.map((obj) => obj.type);
|
||||
const uniqueTypes = [...new Set(allTypes)];
|
||||
if (uniqueTypes.length == 1) {
|
||||
if (uniqueTypes.length === 1) {
|
||||
return uniqueTypes[0];
|
||||
} else {
|
||||
return 'projects_and_folders';
|
||||
}
|
||||
return 'projects_and_folders';
|
||||
},
|
||||
filteredFoldersTree() {
|
||||
if (this.query == '') {
|
||||
if (this.query === '') {
|
||||
return this.foldersTree;
|
||||
} else {
|
||||
return this.foldersTree.map((folder) => {
|
||||
return {
|
||||
folder: folder.folder,
|
||||
children: folder.children.filter((child) => {
|
||||
return child.folder.name.toLowerCase().includes(this.query.toLowerCase());
|
||||
})
|
||||
}
|
||||
}).filter((folder) => {
|
||||
return folder.folder.name.toLowerCase().includes(this.query.toLowerCase()) || folder.children.length > 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
return this.foldersTree.map((folder) => (
|
||||
{
|
||||
folder: folder.folder,
|
||||
children: folder.children.filter((child) => (
|
||||
child.folder.name.toLowerCase().includes(this.query.toLowerCase())
|
||||
)),
|
||||
}
|
||||
)).filter((folder) => (
|
||||
folder.folder.name.toLowerCase().includes(this.query.toLowerCase())
|
||||
|| folder.children.length > 0
|
||||
));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
selectFolder(folderId) {
|
||||
|
@ -107,19 +115,19 @@ export default {
|
|||
submit() {
|
||||
axios.post(this.moveToUrl, {
|
||||
destination_folder_id: this.selectedFolderId || 'root_folder',
|
||||
movables: this.selectedObjects.map((obj) => {
|
||||
return {
|
||||
movables: this.selectedObjects.map((obj) => (
|
||||
{
|
||||
id: obj.id,
|
||||
type: obj.type,
|
||||
}
|
||||
})
|
||||
)),
|
||||
}).then((response) => {
|
||||
this.$emit('move');
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.message, 'danger');
|
||||
})
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -6,14 +6,20 @@
|
|||
@click="opendedFolders[object.folder.id] = !opendedFolders[object.folder.id]"
|
||||
class="sn-icon p-2 pr-1 cursor-pointer"></i>
|
||||
<i v-else class="sn-icon sn-icon-up p-2 pr-1 opacity-0"></i>
|
||||
<div @click="$emit('selectFolder', object.folder.id)" class="cursor-pointer flex items-center pl-1 flex-1 gap-2 text-sn-blue hover:bg-sn-super-light-grey" :class="{'!bg-sn-super-light-blue': object.folder.id == value}">
|
||||
<div @click="$emit('selectFolder', object.folder.id)"
|
||||
class="cursor-pointer flex items-center pl-1 flex-1 gap-2
|
||||
text-sn-blue hover:bg-sn-super-light-grey"
|
||||
:class="{'!bg-sn-super-light-blue': object.folder.id == value}">
|
||||
<i class="sn-icon sn-icon-folder"></i>
|
||||
<div class="flex-1 truncate p-2 pl-0" :title="object.folder.name">
|
||||
{{ object.folder.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MoveTree v-if="opendedFolders[object.folder.id]" :objects="object.children" :value="value" @selectFolder="$emit('selectFolder', $event)" />
|
||||
<MoveTree v-if="opendedFolders[object.folder.id]"
|
||||
:objects="object.children"
|
||||
:value="value"
|
||||
@selectFolder="$emit('selectFolder', $event)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -31,7 +37,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
opendedFolders: {},
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label">
|
||||
{{ i18n.t('projects.index.modal_new_project.modal_title') }}
|
||||
</h4>
|
||||
|
@ -12,7 +14,10 @@
|
|||
<div class="mb-6">
|
||||
<label class="sci-label">{{ i18n.t("projects.index.modal_new_project.name") }}</label>
|
||||
<div class="sci-input-container-v2" :class="{'error': error}" :data-error="error">
|
||||
<input type="text" v-model="name" class="sci-input-field" autofocus="true" :placeholder="i18n.t('projects.index.modal_new_project.name_placeholder')" />
|
||||
<input type="text" v-model="name"
|
||||
class="sci-input-field"
|
||||
autofocus="true"
|
||||
:placeholder="i18n.t('projects.index.modal_new_project.name_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 text-xs items-center">
|
||||
|
@ -29,7 +34,9 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">{{ i18n.t('projects.index.modal_new_project.create') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">
|
||||
{{ i18n.t('projects.index.modal_new_project.create') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -38,18 +45,18 @@
|
|||
|
||||
<script>
|
||||
|
||||
import SelectDropdown from "../../shared/select_dropdown.vue";
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modal_mixin from "../../shared/modal_mixin";
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: "NewProjectModal",
|
||||
name: 'NewProjectModal',
|
||||
props: {
|
||||
createUrl: String,
|
||||
userRolesUrl: String,
|
||||
currentFolderId: String,
|
||||
},
|
||||
mixins: [modal_mixin],
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown,
|
||||
},
|
||||
|
@ -69,17 +76,17 @@ export default {
|
|||
visibility: (this.visible ? 'visible' : 'hidden'),
|
||||
default_public_user_role_id: this.defaultRole,
|
||||
project_folder_id: this.currentFolderId,
|
||||
}
|
||||
},
|
||||
}).then(() => {
|
||||
this.error = null;
|
||||
this.$emit('create');
|
||||
}).catch((error) => {
|
||||
this.error = error.response.data.name;
|
||||
})
|
||||
});
|
||||
},
|
||||
changeRole(role) {
|
||||
this.defaultRole = role;
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label">
|
||||
{{ i18n.t('projects.index.modal_new_project_folder.modal_title') }}
|
||||
</h4>
|
||||
|
@ -12,13 +14,17 @@
|
|||
<div class="mb-6">
|
||||
<label class="sci-label">{{ i18n.t('projects.index.modal_new_project_folder.name') }}</label>
|
||||
<div class="sci-input-container-v2" :class="{'error': error}" :data-error="error">
|
||||
<input type="text" v-model="name" class="sci-input-field" autofocus="true" :placeholder="i18n.t('projects.index.modal_new_project.name_placeholder')" />
|
||||
<input type="text" v-model="name"
|
||||
class="sci-input-field" autofocus="true"
|
||||
:placeholder="i18n.t('projects.index.modal_new_project.name_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">{{ i18n.t('projects.index.modal_new_project.create') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">
|
||||
{{ i18n.t('projects.index.modal_new_project.create') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -27,19 +33,19 @@
|
|||
|
||||
<script>
|
||||
|
||||
import SelectDropdown from "../../shared/select_dropdown.vue";
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modal_mixin from "../../shared/modal_mixin";
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: "NewProjectModal",
|
||||
name: 'NewProjectModal',
|
||||
props: {
|
||||
createFolderUrl: String,
|
||||
userRolesUrl: String,
|
||||
viewMode: String,
|
||||
currentFolderId: String,
|
||||
},
|
||||
mixins: [modal_mixin],
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown,
|
||||
},
|
||||
|
@ -55,18 +61,18 @@ export default {
|
|||
project_folder: {
|
||||
name: this.name,
|
||||
parent_folder_id: this.currentFolderId,
|
||||
archived: (this.viewMode === 'archived')
|
||||
}
|
||||
archived: (this.viewMode === 'archived'),
|
||||
},
|
||||
}).then(() => {
|
||||
this.error = null;
|
||||
this.$emit('create');
|
||||
}).catch((error) => {
|
||||
this.error = error.response.data.name;
|
||||
})
|
||||
});
|
||||
},
|
||||
changeRole(role) {
|
||||
this.defaultRole = role;
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
<div v-for="(user, i) in visibleUsers" :key="i" :title="user.full_name">
|
||||
<img :src="user.avatar" class="w-7 h-7" />
|
||||
</div>
|
||||
<div v-if="hiddenUsers.length > 0" :title="hiddenUsersTitle" class="flex shrink-0 items-center justify-center w-7 h-7 text-xs rounded-full bg-sn-dark-grey text-sn-white">
|
||||
<div v-if="hiddenUsers.length > 0" :title="hiddenUsersTitle"
|
||||
class="flex shrink-0 items-center justify-center w-7 h-7 text-xs rounded-full bg-sn-dark-grey text-sn-white">
|
||||
+{{ hiddenUsers.length }}
|
||||
</div>
|
||||
<div class="flex items-center shrink-0 justify-center w-7 h-7 rounded-full bg-sn-light-grey text-sn-dark-grey">
|
||||
|
@ -17,28 +18,27 @@ export default {
|
|||
name: 'UsersRenderer',
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
users() {
|
||||
return this.params.value || []
|
||||
return this.params.value || [];
|
||||
},
|
||||
visibleUsers() {
|
||||
return this.users.slice(0, 4)
|
||||
return this.users.slice(0, 4);
|
||||
},
|
||||
hiddenUsers() {
|
||||
return this.users.slice(4)
|
||||
return this.users.slice(4);
|
||||
},
|
||||
hiddenUsersTitle() {
|
||||
return this.hiddenUsers.map((user) => user.full_name).join("\u000d")
|
||||
}
|
||||
return this.hiddenUsers.map((user) => user.full_name).join('\u000d');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openAccessModal() {
|
||||
this.params.dtComponent.$emit('access', {} ,[this.params.data]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
this.params.dtComponent.$emit('access', {}, [this.params.data]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
{{ i18n.t('action_toolbar.no_actions') }}
|
||||
</div>
|
||||
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0">
|
||||
<a :class="`rounded flex gap-2 items-center py-1.5 px-2.5 bg-sn-white color-sn-blue hover:no-underline focus:no-underline ${action.button_class}`"
|
||||
<a :class="`rounded flex gap-2 items-center py-1.5 px-2.5
|
||||
bg-sn-white color-sn-blue hover:no-underline focus:no-underline ${action.button_class}`"
|
||||
:href="(['link', 'remote-modal']).includes(action.type) ? action.path : '#'"
|
||||
:id="action.button_id"
|
||||
:title="action.label"
|
||||
|
@ -22,51 +23,53 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ActionToolbar',
|
||||
props: {
|
||||
actionsUrl: { type: String, required: true },
|
||||
params: { type: Object }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actions: [],
|
||||
multiple: false,
|
||||
reloadCallback: null,
|
||||
loaded: false,
|
||||
loading: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
params() {
|
||||
this.loadActions()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
export default {
|
||||
name: 'ActionToolbar',
|
||||
props: {
|
||||
actionsUrl: { type: String, required: true },
|
||||
params: { type: Object },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actions: [],
|
||||
multiple: false,
|
||||
reloadCallback: null,
|
||||
loaded: false,
|
||||
loading: true,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
params() {
|
||||
this.loadActions();
|
||||
},
|
||||
methods: {
|
||||
loadActions() {
|
||||
this.loading = true;
|
||||
this.loaded = false;
|
||||
$.get(`${this.actionsUrl}?${new URLSearchParams(this.params).toString()}`, (data) => {
|
||||
this.actions = data.actions;
|
||||
this.loading = false;
|
||||
this.loaded = true;
|
||||
});
|
||||
},
|
||||
doAction(action, event) {
|
||||
switch(action.type) {
|
||||
case 'emit':
|
||||
event.preventDefault();
|
||||
this.$emit('toolbar:action', action);
|
||||
// do nothing, this is handled by legacy code based on the button class
|
||||
break;
|
||||
case 'link':
|
||||
// do nothing, already handled by href
|
||||
break;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadActions();
|
||||
},
|
||||
methods: {
|
||||
loadActions() {
|
||||
this.loading = true;
|
||||
this.loaded = false;
|
||||
$.get(`${this.actionsUrl}?${new URLSearchParams(this.params).toString()}`, (data) => {
|
||||
this.actions = data.actions;
|
||||
this.loading = false;
|
||||
this.loaded = true;
|
||||
});
|
||||
},
|
||||
doAction(action, event) {
|
||||
switch (action.type) {
|
||||
case 'emit':
|
||||
event.preventDefault();
|
||||
this.$emit('toolbar:action', action);
|
||||
// do nothing, this is handled by legacy code based on the button class
|
||||
break;
|
||||
case 'link':
|
||||
// do nothing, already handled by href
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
export default {
|
||||
methods: {
|
||||
itemSelected() {
|
||||
let item = this.dtComponent.selectedRows.find((item) => {
|
||||
return item.id == this.params.id;
|
||||
});
|
||||
const item = this.dtComponent.selectedRows.find((i) => (i.id === this.params.id));
|
||||
|
||||
if (item) {
|
||||
this.dtComponent.selectedRows = this.dtComponent.selectedRows.filter((item) => {
|
||||
return item.id != this.params.id;
|
||||
});
|
||||
this.dtComponent.selectedRows = this.dtComponent.selectedRows
|
||||
.filter((i) => (i.id !== this.params.id));
|
||||
} else {
|
||||
this.dtComponent.selectedRows.push(this.params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -35,13 +35,12 @@ export default {
|
|||
currentPage: {
|
||||
type: Number,
|
||||
required: true,
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
pages() {
|
||||
let pages = [];
|
||||
for (let i = 1; i <= this.totalPage; i++) {
|
||||
const pages = [];
|
||||
for (let i = 1; i <= this.totalPage; i += 1) {
|
||||
if (i >= this.currentPage - 2 || this.totalPage <= 5) {
|
||||
pages.push(i);
|
||||
}
|
||||
|
@ -51,7 +50,7 @@ export default {
|
|||
}
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -13,54 +13,53 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MenuDropdown from '../menu_dropdown.vue'
|
||||
import MenuDropdown from '../menu_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
|
||||
export default {
|
||||
name: 'RowMenuRenderer',
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actionsMenu: []
|
||||
}
|
||||
actionsMenu: [],
|
||||
};
|
||||
},
|
||||
components: {
|
||||
MenuDropdown
|
||||
MenuDropdown,
|
||||
},
|
||||
computed: {
|
||||
formattedList() {
|
||||
return this.actionsMenu.map((item) => {
|
||||
let newItem = { text: item.label }
|
||||
if (item.type == 'emit') {
|
||||
newItem.emit = item.name
|
||||
const newItem = { text: item.label };
|
||||
if (item.type === 'emit') {
|
||||
newItem.emit = item.name;
|
||||
}
|
||||
if (item.type == 'link') {
|
||||
newItem.url = item.path
|
||||
if (item.type === 'link') {
|
||||
newItem.url = item.path;
|
||||
}
|
||||
|
||||
newItem.params = item
|
||||
newItem.params = item;
|
||||
|
||||
return newItem
|
||||
})
|
||||
}
|
||||
return newItem;
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
loadActions() {
|
||||
if (this.actionsMenu.length > 0) return
|
||||
|
||||
if (this.actionsMenu.length > 0) return;
|
||||
axios.get(this.params.data.urls.actions)
|
||||
.then((response) => {
|
||||
this.actionsMenu = response.data.actions
|
||||
})
|
||||
this.actionsMenu = response.data.actions;
|
||||
});
|
||||
},
|
||||
handleEvents(event, option) {
|
||||
const dt = this.params.dtComponent
|
||||
dt.$emit(event, option.params, [this.params.data])
|
||||
}
|
||||
}
|
||||
}
|
||||
const dt = this.params.dtComponent;
|
||||
dt.$emit(event, option.params, [this.params.data]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -42,7 +42,11 @@
|
|||
:CheckboxSelectionCallback="withCheckboxes"
|
||||
>
|
||||
</ag-grid-vue>
|
||||
<ActionToolbar v-if="selectedRows.length > 0 && actionsUrl" :actionsUrl="actionsUrl" :params="actionsParams" @toolbar:action="emitAction" />
|
||||
<ActionToolbar
|
||||
v-if="selectedRows.length > 0 && actionsUrl"
|
||||
:actionsUrl="actionsUrl"
|
||||
:params="actionsParams"
|
||||
@toolbar:action="emitAction" />
|
||||
</div>
|
||||
<div class="flex items-center py-4">
|
||||
<div class="mr-auto">
|
||||
|
@ -67,10 +71,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { AgGridVue } from "ag-grid-vue3";
|
||||
import { AgGridVue } from 'ag-grid-vue3';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import SelectDropdown from '../select_dropdown.vue';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import Pagination from './pagination.vue';
|
||||
import CustomHeader from './tableHeader';
|
||||
import ActionToolbar from './action_toolbar.vue';
|
||||
|
@ -78,7 +82,7 @@ import Toolbar from './toolbar.vue';
|
|||
import RowMenuRenderer from './row_menu_renderer.vue';
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
name: 'App',
|
||||
props: {
|
||||
withCheckboxes: {
|
||||
type: Boolean,
|
||||
|
@ -105,11 +109,11 @@ export default {
|
|||
},
|
||||
toolbarActions: {
|
||||
type: Object,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
reloadingTable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
activePageUrl: {
|
||||
type: String,
|
||||
|
@ -119,15 +123,15 @@ export default {
|
|||
},
|
||||
currentViewMode: {
|
||||
type: String,
|
||||
default: 'active'
|
||||
default: 'active',
|
||||
},
|
||||
viewRenders: {
|
||||
type: Object,
|
||||
},
|
||||
filters: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -135,7 +139,7 @@ export default {
|
|||
gridApi: null,
|
||||
columnApi: null,
|
||||
defaultColDef: {
|
||||
resizable: true
|
||||
resizable: true,
|
||||
},
|
||||
perPage: 20,
|
||||
page: 1,
|
||||
|
@ -146,7 +150,7 @@ export default {
|
|||
initializing: true,
|
||||
activeFilters: {},
|
||||
currentViewRender: 'table',
|
||||
cardCheckboxes: []
|
||||
cardCheckboxes: [],
|
||||
};
|
||||
},
|
||||
components: {
|
||||
|
@ -157,11 +161,11 @@ export default {
|
|||
agColumnHeader: CustomHeader,
|
||||
ActionToolbar,
|
||||
Toolbar,
|
||||
RowMenuRenderer
|
||||
RowMenuRenderer,
|
||||
},
|
||||
computed: {
|
||||
perPageOptions() {
|
||||
return [10, 20, 50, 100].map(value => [ value, `${value} ${this.i18n.t('datatable.rows')}` ]);
|
||||
return [10, 20, 50, 100].map((value) => ([value, `${value} ${this.i18n.t('datatable.rows')}`]));
|
||||
},
|
||||
tableState() {
|
||||
if (!localStorage.getItem(`datatable:${this.tableId}_columns_state`)) return null;
|
||||
|
@ -170,40 +174,38 @@ export default {
|
|||
},
|
||||
actionsParams() {
|
||||
return {
|
||||
items: JSON.stringify(this.selectedRows.map(row => { return {id: row.id, type: row.type} }))
|
||||
}
|
||||
items: JSON.stringify(this.selectedRows.map((row) => ({ id: row.id, type: row.type }))),
|
||||
};
|
||||
},
|
||||
gridOptions() {
|
||||
return {
|
||||
suppressCellFocus: true
|
||||
}
|
||||
suppressCellFocus: true,
|
||||
};
|
||||
},
|
||||
extendedColumnDefs() {
|
||||
let columns = this.columnDefs.map(column => {
|
||||
return {
|
||||
...column,
|
||||
cellRendererParams: {
|
||||
dtComponent: this
|
||||
}
|
||||
}
|
||||
});
|
||||
const columns = this.columnDefs.map((column) => ({
|
||||
...column,
|
||||
cellRendererParams: {
|
||||
dtComponent: this,
|
||||
},
|
||||
}));
|
||||
|
||||
if (this.withCheckboxes) {
|
||||
columns.unshift({
|
||||
field: "checkbox",
|
||||
field: 'checkbox',
|
||||
headerCheckboxSelection: true,
|
||||
headerCheckboxSelectionFilteredOnly: true,
|
||||
checkboxSelection: true,
|
||||
width: 48,
|
||||
minWidth: 48,
|
||||
resizable: false,
|
||||
pinned: 'left'
|
||||
pinned: 'left',
|
||||
});
|
||||
}
|
||||
|
||||
if (this.withRowMenu) {
|
||||
columns.push({
|
||||
field: "rowMenu",
|
||||
field: 'rowMenu',
|
||||
headerName: '',
|
||||
width: 42,
|
||||
minWidth: 42,
|
||||
|
@ -211,22 +213,28 @@ export default {
|
|||
sortable: false,
|
||||
cellRenderer: 'RowMenuRenderer',
|
||||
cellRendererParams: {
|
||||
dtComponent: this
|
||||
dtComponent: this,
|
||||
},
|
||||
pinned: 'right',
|
||||
cellStyle: {padding: 0, display: 'flex', justifyContent: 'center', alignItems: 'center', overflow: 'visible'}
|
||||
cellStyle: {
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
overflow: 'visible',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
reloadingTable() {
|
||||
if (this.reloadingTable) {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
|
@ -237,7 +245,11 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
formatData(data) {
|
||||
return data.map( (item) => Object.assign({}, item.attributes, { id: item.id, type: item.type }) );
|
||||
return data.map((item) => ({
|
||||
...item.attributes,
|
||||
id: item.id,
|
||||
type: item.type,
|
||||
}));
|
||||
},
|
||||
resize() {
|
||||
if (this.tableState) return;
|
||||
|
@ -253,8 +265,8 @@ export default {
|
|||
order: this.order,
|
||||
search: this.searchValue,
|
||||
view_mode: this.currentViewMode,
|
||||
filters: this.activeFilters
|
||||
}
|
||||
filters: this.activeFilters,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
this.selectedRows = [];
|
||||
|
@ -262,7 +274,7 @@ export default {
|
|||
this.rowData = this.formatData(response.data.data);
|
||||
this.totalPage = response.data.meta.total_pages;
|
||||
this.$emit('tableReloaded');
|
||||
})
|
||||
});
|
||||
},
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
|
@ -271,14 +283,14 @@ export default {
|
|||
if (this.tableState) {
|
||||
this.columnApi.applyColumnState({
|
||||
state: this.tableState,
|
||||
applyOrder: true
|
||||
applyOrder: true,
|
||||
});
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.initializing = false;
|
||||
}, 200);
|
||||
},
|
||||
onFirstDataRendered(params) {
|
||||
onFirstDataRendered() {
|
||||
this.resize();
|
||||
},
|
||||
setPerPage(value) {
|
||||
|
@ -291,13 +303,14 @@ export default {
|
|||
this.loadData();
|
||||
},
|
||||
setOrder() {
|
||||
const orderState = this.columnApi.getColumnState().filter(column => column.sort).map(column => {
|
||||
return {
|
||||
const orderState = this.columnApi.getColumnState()
|
||||
.filter((column) => column.sort)
|
||||
.map((column) => ({
|
||||
column: column.colId,
|
||||
dir: column.sort
|
||||
}
|
||||
});
|
||||
this.order = orderState[0];
|
||||
dir: column.sort,
|
||||
}));
|
||||
const [order] = orderState;
|
||||
this.order = order;
|
||||
this.saveColumnsState();
|
||||
this.loadData();
|
||||
},
|
||||
|
@ -319,7 +332,7 @@ export default {
|
|||
},
|
||||
clickCell(e) {
|
||||
if (e.column.colId !== 'rowMenu' && e.column.userProvidedColDef.notSelectable !== true) {
|
||||
e.node.setSelected(true);
|
||||
e.node.setSelected(true);
|
||||
}
|
||||
},
|
||||
applyFilters(filters) {
|
||||
|
@ -332,7 +345,7 @@ export default {
|
|||
this.currentViewRender = view;
|
||||
this.initializing = true;
|
||||
this.selectedRows = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export default {
|
||||
template: `
|
||||
<div class="w-full grid items-center gap-2 grid-cols-[auto_1.5rem] cursor-pointer" @click="onSortRequested((activeSort == 'asc' ? 'desc' : 'asc'), $event)">
|
||||
<div class="w-full grid items-center gap-2 grid-cols-[auto_1.5rem] cursor-pointer"
|
||||
@click="onSortRequested((activeSort == 'asc' ? 'desc' : 'asc'), $event)">
|
||||
<div v-if="params.html" class="customHeaderLabel truncate" v-html="params.html"></div>
|
||||
<div v-else class="customHeaderLabel truncate">{{ params.displayName }}</div>
|
||||
<div v-if="activeSort == 'asc'" class="customSortDownLabel text-sn-sleepy-grey">
|
||||
|
|
|
@ -39,7 +39,9 @@
|
|||
<i :class="action.icon"></i>
|
||||
{{ action.label }}
|
||||
</a>
|
||||
<div v-if="filters.length == 0" class="sci-input-container-v2" :class="{'w-48': showSearch, 'w-11': !showSearch}">
|
||||
<div v-if="filters.length == 0"
|
||||
class="sci-input-container-v2"
|
||||
:class="{'w-48': showSearch, 'w-11': !showSearch}">
|
||||
<input
|
||||
ref="searchInput"
|
||||
class="sci-input-field !pr-8"
|
||||
|
@ -58,7 +60,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MenuDropdown from '../menu_dropdown.vue'
|
||||
import MenuDropdown from '../menu_dropdown.vue';
|
||||
import FilterDropdown from '../filters/filter_dropdown.vue';
|
||||
|
||||
export default {
|
||||
|
@ -66,7 +68,7 @@ export default {
|
|||
props: {
|
||||
toolbarActions: {
|
||||
type: Object,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
searchValue: {
|
||||
type: String,
|
||||
|
@ -79,57 +81,57 @@ export default {
|
|||
},
|
||||
currentViewMode: {
|
||||
type: String,
|
||||
default: 'active'
|
||||
default: 'active',
|
||||
},
|
||||
filters: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
viewRenders: {
|
||||
type: Array,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
currentViewRender: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
MenuDropdown,
|
||||
FilterDropdown
|
||||
FilterDropdown,
|
||||
},
|
||||
computed: {
|
||||
viewModesMenu() {
|
||||
return [
|
||||
{ text: this.i18n.t('projects.index.active'), url: this.activePageUrl},
|
||||
{ text: this.i18n.t('projects.index.archived'), url: this.archivedPageUrl }
|
||||
]
|
||||
{ text: this.i18n.t('projects.index.active'), url: this.activePageUrl },
|
||||
{ text: this.i18n.t('projects.index.archived'), url: this.archivedPageUrl },
|
||||
];
|
||||
},
|
||||
viewRendersMenu() {
|
||||
return this.viewRenders.map((view) => {
|
||||
const type = view.type;
|
||||
const { type } = view;
|
||||
switch (type) {
|
||||
case 'cards':
|
||||
return { text: this.i18n.t('toolbar.cards_view'), emit: 'setCardsView'};
|
||||
return { text: this.i18n.t('toolbar.cards_view'), emit: 'setCardsView' };
|
||||
case 'table':
|
||||
return { text: this.i18n.t('toolbar.table_view'), emit: 'setTableView'};
|
||||
return { text: this.i18n.t('toolbar.table_view'), emit: 'setTableView' };
|
||||
default:
|
||||
return view;
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showSearch: false
|
||||
}
|
||||
showSearch: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
searchValue() {
|
||||
if (this.searchValue.length > 0) {
|
||||
this.openSearch();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openSearch() {
|
||||
|
@ -141,18 +143,20 @@ export default {
|
|||
}
|
||||
},
|
||||
doAction(action, event) {
|
||||
switch(action.type) {
|
||||
switch (action.type) {
|
||||
case 'emit':
|
||||
event.preventDefault();
|
||||
this.$emit('toolbar:action', action);
|
||||
break;
|
||||
case 'link':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
applyFilters(filters) {
|
||||
this.$emit('applyFilters', filters);
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -50,150 +50,148 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import VueDatePicker from '@vuepic/vue-datepicker';
|
||||
import VueDatePicker from '@vuepic/vue-datepicker';
|
||||
|
||||
export default {
|
||||
name: 'DateTimePicker',
|
||||
props: {
|
||||
mode: { type: String, default: 'datetime' },
|
||||
clearable: { type: Boolean, default: false },
|
||||
teleport: { type: Boolean, default: true },
|
||||
defaultValue: { type: Date, required: false },
|
||||
placeholder: { type: String },
|
||||
standAlone: { type: Boolean, default: false, required: false },
|
||||
dateClassName: { type: String, default: '' },
|
||||
timeClassName: { type: String, default: '' },
|
||||
disabled: { type: Boolean, default: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
manualUpdate: false,
|
||||
datetime: this.defaultValue,
|
||||
time: null,
|
||||
markers: [
|
||||
{
|
||||
date: new Date(),
|
||||
type: 'dot',
|
||||
color: '#3B99FD',
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.defaultValue) {
|
||||
export default {
|
||||
name: 'DateTimePicker',
|
||||
props: {
|
||||
mode: { type: String, default: 'datetime' },
|
||||
clearable: { type: Boolean, default: false },
|
||||
teleport: { type: Boolean, default: true },
|
||||
defaultValue: { type: Date, required: false },
|
||||
placeholder: { type: String },
|
||||
standAlone: { type: Boolean, default: false, required: false },
|
||||
dateClassName: { type: String, default: '' },
|
||||
timeClassName: { type: String, default: '' },
|
||||
disabled: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
manualUpdate: false,
|
||||
datetime: this.defaultValue,
|
||||
time: null,
|
||||
markers: [
|
||||
{
|
||||
date: new Date(),
|
||||
type: 'dot',
|
||||
color: '#3B99FD',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.defaultValue) {
|
||||
this.time = {
|
||||
hours: this.defaultValue.getHours(),
|
||||
minutes: this.defaultValue.getMinutes(),
|
||||
};
|
||||
}
|
||||
},
|
||||
components: {
|
||||
VueDatePicker,
|
||||
},
|
||||
watch: {
|
||||
defaultValue() {
|
||||
this.datetime = this.defaultValue;
|
||||
if (this.defaultValue instanceof Date) {
|
||||
this.time = {
|
||||
hours: this.defaultValue.getHours(),
|
||||
minutes:this.defaultValue.getMinutes(),
|
||||
}
|
||||
hours: this.defaultValue.getHours(),
|
||||
minutes: this.defaultValue.getMinutes(),
|
||||
};
|
||||
}
|
||||
},
|
||||
components: {
|
||||
VueDatePicker
|
||||
},
|
||||
watch: {
|
||||
defaultValue: function () {
|
||||
this.datetime = this.defaultValue;
|
||||
if (this.defaultValue instanceof Date) {
|
||||
this.time = {
|
||||
hours: this.defaultValue.getHours(),
|
||||
minutes: this.defaultValue.getMinutes()
|
||||
}
|
||||
}
|
||||
},
|
||||
datetime: function () {
|
||||
if (this.mode == 'time') {
|
||||
datetime() {
|
||||
if (this.mode === 'time') {
|
||||
this.time = null;
|
||||
|
||||
this.time = null;
|
||||
|
||||
if (this.datetime instanceof Date) {
|
||||
if (this.datetime instanceof Date) {
|
||||
this.time = {
|
||||
hours: this.datetime.getHours(),
|
||||
minutes: this.datetime.getMinutes()
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (this.manualUpdate) {
|
||||
this.manualUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( this.datetime == null) {
|
||||
this.$emit('cleared');
|
||||
}
|
||||
|
||||
if (this.defaultValue != this.datetime) {
|
||||
this.$emit('change', this.datetime);
|
||||
}
|
||||
},
|
||||
time: function () {
|
||||
if (this.manualUpdate) {
|
||||
this.manualUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mode != 'time') return;
|
||||
|
||||
let newDate;
|
||||
|
||||
if (this.time) {
|
||||
newDate = new Date();
|
||||
newDate.setHours(this.time.hours);
|
||||
newDate.setMinutes(this.time.minutes);
|
||||
} else {
|
||||
newDate = {
|
||||
hours: null,
|
||||
minutes: null
|
||||
minutes: this.datetime.getMinutes(),
|
||||
};
|
||||
this.$emit('cleared');
|
||||
}
|
||||
|
||||
if (this.defaultValue != newDate) {
|
||||
this.$emit('change', newDate);
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
compDatetime: {
|
||||
get () {
|
||||
if (this.mode == 'time') {
|
||||
return this.time
|
||||
} else {
|
||||
return this.datetime
|
||||
}
|
||||
},
|
||||
set (val) {
|
||||
if (this.mode == 'time') {
|
||||
this.time = val
|
||||
} else {
|
||||
this.datetime = val
|
||||
}
|
||||
}
|
||||
},
|
||||
format() {
|
||||
if (this.mode == 'time') return 'HH:mm'
|
||||
if (this.mode == 'date') return document.body.dataset.datetimePickerFormatVue
|
||||
return `${document.body.dataset.datetimePickerFormatVue} HH:mm`
|
||||
|
||||
if (this.manualUpdate) {
|
||||
this.manualUpdate = false;
|
||||
return;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', this.close);
|
||||
},
|
||||
unmounted() {
|
||||
window.removeEventListener('resize', this.close);
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.$refs.datetimePicker.closeMenu();
|
||||
},
|
||||
closedHandler() {
|
||||
this.$emit('closed');
|
||||
},
|
||||
clearedHandler() {
|
||||
|
||||
if (this.datetime == null) {
|
||||
this.$emit('cleared');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.defaultValue !== this.datetime) {
|
||||
this.$emit('change', this.datetime);
|
||||
}
|
||||
},
|
||||
time() {
|
||||
if (this.manualUpdate) {
|
||||
this.manualUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mode !== 'time') return;
|
||||
|
||||
let newDate;
|
||||
|
||||
if (this.time) {
|
||||
newDate = new Date();
|
||||
newDate.setHours(this.time.hours);
|
||||
newDate.setMinutes(this.time.minutes);
|
||||
} else {
|
||||
newDate = {
|
||||
hours: null,
|
||||
minutes: null,
|
||||
};
|
||||
this.$emit('cleared');
|
||||
}
|
||||
|
||||
if (this.defaultValue !== newDate) {
|
||||
this.$emit('change', newDate);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
compDatetime: {
|
||||
get() {
|
||||
if (this.mode === 'time') {
|
||||
return this.time;
|
||||
}
|
||||
return this.datetime;
|
||||
},
|
||||
set(val) {
|
||||
if (this.mode === 'time') {
|
||||
this.time = val;
|
||||
} else {
|
||||
this.datetime = val;
|
||||
}
|
||||
},
|
||||
},
|
||||
format() {
|
||||
if (this.mode === 'time') return 'HH:mm';
|
||||
if (this.mode === 'date') return document.body.dataset.datetimePickerFormatVue;
|
||||
return `${document.body.dataset.datetimePickerFormatVue} HH:mm`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('resize', this.close);
|
||||
},
|
||||
unmounted() {
|
||||
window.removeEventListener('resize', this.close);
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.$refs.datetimePicker.closeMenu();
|
||||
},
|
||||
closedHandler() {
|
||||
this.$emit('closed');
|
||||
},
|
||||
clearedHandler() {
|
||||
this.$emit('cleared');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
<template v-if="!isOpen || !searchable">
|
||||
<div class="truncate" v-if="labelRenderer && label" v-html="label"></div>
|
||||
<div class="truncate" v-else-if="label">{{ label }}</div>
|
||||
<div class="text-sn-grey truncate" v-else>{{ placeholder || this.i18n.t('general.select_dropdown.placeholder') }}</div>
|
||||
<div class="text-sn-grey truncate" v-else>
|
||||
{{ placeholder || this.i18n.t('general.select_dropdown.placeholder') }}
|
||||
</div>
|
||||
</template>
|
||||
<input type="text"
|
||||
ref="search"
|
||||
|
@ -21,12 +23,17 @@
|
|||
:placeholder="label || placeholder || this.i18n.t('general.select_dropdown.placeholder')"
|
||||
class="w-full border-0 outline-none pl-0 placeholder:text-sn-grey" />
|
||||
<i v-if="canClear" @click="clear" class="sn-icon ml-auto sn-icon-close"></i>
|
||||
<i v-else class="sn-icon ml-auto" :class="{ 'sn-icon-down': !isOpen, 'sn-icon-up': isOpen, 'text-sn-grey': disabled}"></i>
|
||||
<i v-else class="sn-icon ml-auto"
|
||||
:class="{ 'sn-icon-down': !isOpen, 'sn-icon-up': isOpen, 'text-sn-grey': disabled}"></i>
|
||||
</div>
|
||||
<teleport to="body">
|
||||
<div v-if="isOpen" ref="flyout" class="bg-white inline-block sn-shadow-menu-sm rounded w-full fixed z-[3000]">
|
||||
<div v-if="isOpen" ref="flyout"
|
||||
class="sn-dropdown bg-white inline-block sn-shadow-menu-sm rounded w-full
|
||||
fixed z-[3000]">
|
||||
<div v-if="multiple && withCheckboxes" class="p-2.5 pb-0">
|
||||
<div @click="selectAll" :class="sizeClass" class="border-x-0 border-transparent border-solid border-b-sn-light-grey py-1.5 px-3 cursor-pointer flex items-center gap-2 shrink-0">
|
||||
<div @click="selectAll" :class="sizeClass"
|
||||
class="border-x-0 border-transparent border-solid border-b-sn-light-grey
|
||||
py-1.5 px-3 cursor-pointer flex items-center gap-2 shrink-0">
|
||||
<div class="sn-checkbox-icon"
|
||||
:class="selectAllState"
|
||||
></div>
|
||||
|
@ -62,8 +69,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import FixedFlyoutMixin from './mixins/fixed_flyout.js';
|
||||
import { vOnClickOutside } from '@vueuse/components'
|
||||
|
||||
export default {
|
||||
name: 'SelectDropdown',
|
||||
|
@ -71,7 +78,7 @@ export default {
|
|||
value: { type: [String, Number, Array] },
|
||||
options: { type: Array, default: () => [] },
|
||||
optionsUrl: { type: String },
|
||||
placeholder: { type: String},
|
||||
placeholder: { type: String },
|
||||
noOptionsPlaceholder: { type: String },
|
||||
fewOptionsPlaceholder: { type: String },
|
||||
allOptionsPlaceholder: { type: String },
|
||||
|
@ -85,7 +92,7 @@ export default {
|
|||
clearable: { type: Boolean, default: false },
|
||||
},
|
||||
directives: {
|
||||
'click-outside': vOnClickOutside
|
||||
'click-outside': vOnClickOutside,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -94,51 +101,50 @@ export default {
|
|||
fetchedOptions: [],
|
||||
selectAllState: 'unchecked',
|
||||
query: '',
|
||||
fixedWidth: true
|
||||
}
|
||||
fixedWidth: true,
|
||||
};
|
||||
},
|
||||
mixins: [FixedFlyoutMixin],
|
||||
computed: {
|
||||
sizeClass() {
|
||||
switch (this.size) {
|
||||
case 'xs':
|
||||
return 'min-h-[36px]'
|
||||
return 'min-h-[36px]';
|
||||
case 'sm':
|
||||
return 'min-h-[40px]'
|
||||
return 'min-h-[40px]';
|
||||
case 'md':
|
||||
return 'min-h-[44px]'
|
||||
return 'min-h-[44px]';
|
||||
default:
|
||||
return 'min-h-[44px]';
|
||||
}
|
||||
},
|
||||
canClear() {
|
||||
return this.clearable && this.label && this.isOpen
|
||||
return this.clearable && this.label && this.isOpen;
|
||||
},
|
||||
rawOptions() {
|
||||
if (this.optionsUrl) {
|
||||
return this.fetchedOptions
|
||||
} else {
|
||||
return this.options
|
||||
return this.fetchedOptions;
|
||||
}
|
||||
return this.options;
|
||||
},
|
||||
filteredOptions() {
|
||||
if (this.query.length > 0 && !this.optionsUrl ) {
|
||||
return this.rawOptions.filter(option => {
|
||||
return option[1].toLowerCase().includes(this.query.toLowerCase())
|
||||
})
|
||||
} else {
|
||||
return this.rawOptions
|
||||
if (this.query.length > 0 && !this.optionsUrl) {
|
||||
return this.rawOptions.filter((option) => (
|
||||
option[1].toLowerCase().includes(this.query.toLowerCase())
|
||||
));
|
||||
}
|
||||
return this.rawOptions;
|
||||
},
|
||||
label() {
|
||||
if (this.multiple) {
|
||||
return this.multipleLabel
|
||||
} else {
|
||||
return this.singleLabel
|
||||
return this.multipleLabel;
|
||||
}
|
||||
return this.singleLabel;
|
||||
},
|
||||
|
||||
singleLabel() {
|
||||
const option = this.rawOptions.find(option => option[0] === this.newValue)
|
||||
return this.renderLabel(option)
|
||||
const option = this.rawOptions.find((i) => i[0] === this.newValue);
|
||||
return this.renderLabel(option);
|
||||
},
|
||||
multipleLabel() {
|
||||
if (!this.newValue) return false;
|
||||
|
@ -147,29 +153,31 @@ export default {
|
|||
|
||||
if (this.newValue.length === 0) {
|
||||
return false;
|
||||
} else if (this.newValue.length === 1) {
|
||||
this.selectAllState = 'indeterminate'
|
||||
return this.renderLabel(this.rawOptions.find(option => option[0] === this.newValue[0]))
|
||||
} else if (this.newValue.length === this.rawOptions.length) {
|
||||
this.selectAllState = 'checked';
|
||||
return this.allOptionsPlaceholder || this.i18n.t('general.select_dropdown.all_options_placeholder')
|
||||
} else {
|
||||
this.selectAllState = 'indeterminate'
|
||||
return `${this.newValue.length} ${this.fewOptionsPlaceholder || this.i18n.t('general.select_dropdown.few_options_placeholder')}`
|
||||
}
|
||||
if (this.newValue.length === 1) {
|
||||
this.selectAllState = 'indeterminate';
|
||||
return this.renderLabel(this.rawOptions.find((option) => option[0] === this.newValue[0]));
|
||||
}
|
||||
if (this.newValue.length === this.rawOptions.length) {
|
||||
this.selectAllState = 'checked';
|
||||
return this.allOptionsPlaceholder || this.i18n.t('general.select_dropdown.all_options_placeholder');
|
||||
}
|
||||
this.selectAllState = 'indeterminate';
|
||||
return `${this.newValue.length} ${
|
||||
this.fewOptionsPlaceholder || this.i18n.t('general.select_dropdown.few_options_placeholder')
|
||||
}`;
|
||||
},
|
||||
valueChanged() {
|
||||
if (this.multiple) {
|
||||
return !this.compareArrays(this.newValue, this.value)
|
||||
} else {
|
||||
return this.newValue != this.value
|
||||
return !this.compareArrays(this.newValue, this.value);
|
||||
}
|
||||
}
|
||||
return this.newValue !== this.value;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.newValue = this.value;
|
||||
if (!this.newValue && this.multiple) {
|
||||
this.newValue = []
|
||||
this.newValue = [];
|
||||
}
|
||||
this.fetchOptions();
|
||||
},
|
||||
|
@ -179,7 +187,7 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
this.setPosition();
|
||||
this.$refs.search?.focus();
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
query() {
|
||||
|
@ -191,77 +199,81 @@ export default {
|
|||
if (!option) return false;
|
||||
|
||||
if (this.labelRenderer) {
|
||||
return this.labelRenderer(option)
|
||||
} else {
|
||||
return option[1]
|
||||
return this.labelRenderer(option);
|
||||
}
|
||||
return option[1];
|
||||
},
|
||||
valueSelected(value) {
|
||||
if (!this.newValue) return false;
|
||||
if (this.multiple) {
|
||||
return this.newValue.includes(value);
|
||||
} else {
|
||||
return this.newValue == value;
|
||||
}
|
||||
return this.newValue === value;
|
||||
},
|
||||
open() {
|
||||
if (!this.disabled) this.isOpen = true
|
||||
if (!this.disabled) this.isOpen = true;
|
||||
},
|
||||
clear() {
|
||||
this.newValue = this.multiple ? [] : null;
|
||||
this.query = '';
|
||||
this.$emit('change', this.newValue)
|
||||
this.$emit('change', this.newValue);
|
||||
},
|
||||
close() {
|
||||
close(e) {
|
||||
if (e && e.target.closest('.sn-dropdown')) return;
|
||||
|
||||
if (!this.isOpen) return;
|
||||
|
||||
this.isOpen = false
|
||||
if (this.valueChanged) {
|
||||
this.$emit('change', this.newValue)
|
||||
}
|
||||
this.query = '';
|
||||
this.$nextTick(() => {
|
||||
this.isOpen = false;
|
||||
if (this.valueChanged) {
|
||||
this.$emit('change', this.newValue);
|
||||
}
|
||||
this.query = '';
|
||||
});
|
||||
},
|
||||
setValue(value) {
|
||||
if(this.multiple) {
|
||||
if (this.multiple) {
|
||||
if (this.newValue.includes(value)) {
|
||||
this.newValue = this.newValue.filter(v => v != value)
|
||||
this.newValue = this.newValue.filter((v) => v !== value);
|
||||
} else {
|
||||
this.newValue.push(value)
|
||||
this.newValue.push(value);
|
||||
}
|
||||
} else {
|
||||
this.newValue = value
|
||||
this.close()
|
||||
this.newValue = value;
|
||||
this.$nextTick(() => {
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
},
|
||||
selectAll() {
|
||||
if (this.selectAllState === 'checked') {
|
||||
this.newValue = []
|
||||
this.newValue = [];
|
||||
} else {
|
||||
this.newValue = this.rawOptions.map(option => option[0])
|
||||
this.newValue = this.rawOptions.map((option) => option[0]);
|
||||
}
|
||||
this.$emit('change', this.newValue)
|
||||
this.$emit('change', this.newValue);
|
||||
},
|
||||
fetchOptions() {
|
||||
if (this.optionsUrl) {
|
||||
fetch(`${this.optionsUrl}?query=${this.query || ''}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
this.fetchedOptions = data.data;
|
||||
this.$nextTick(() => {
|
||||
this.setPosition();
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
compareArrays(arr1, arr2) {
|
||||
if (!arr1 || !arr2) return false;
|
||||
if (arr1.length !== arr2.length) return false;
|
||||
|
||||
for (let i = 0; i < arr1.length; i++) {
|
||||
for (let i = 0; i < arr1.length; i += 1) {
|
||||
if (!arr2.includes(arr1[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -33,10 +33,14 @@ module Lists
|
|||
end
|
||||
|
||||
def urls
|
||||
urls_list = {}
|
||||
urls_list[:show] = table_experiment_path(object)
|
||||
|
||||
urls_list
|
||||
{
|
||||
show: table_experiment_path(object),
|
||||
actions: actions_toolbar_experiments_path(items: [{ id: object.id }].to_json),
|
||||
projects_to_clone: projects_to_clone_experiment_path(object),
|
||||
projects_to_move: projects_to_move_experiment_path(object),
|
||||
clone: clone_experiment_path(object),
|
||||
move: move_experiment_path(object)
|
||||
}
|
||||
end
|
||||
|
||||
def workflow_img
|
||||
|
|
|
@ -39,10 +39,8 @@ module Toolbars
|
|||
name: 'restore',
|
||||
label: I18n.t('experiments.toolbar.restore_button'),
|
||||
icon: 'sn-icon sn-icon-restore',
|
||||
button_class: 'restore-experiments-btn',
|
||||
path: restore_group_project_experiments_path(project_id: @experiments.first.project_id),
|
||||
type: :request,
|
||||
request_method: :post
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -57,9 +55,7 @@ module Toolbars
|
|||
name: 'edit',
|
||||
label: I18n.t('experiments.index.edit_option'),
|
||||
icon: 'sn-icon sn-icon-edit',
|
||||
button_class: 'edit-btn',
|
||||
path: edit_experiment_path(experiment),
|
||||
type: 'remote-modal'
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -80,9 +76,7 @@ module Toolbars
|
|||
name: 'access',
|
||||
label: I18n.t('general.access'),
|
||||
icon: 'sn-icon sn-icon-project-member-access',
|
||||
button_class: 'access-btn',
|
||||
path: path,
|
||||
type: 'remote-modal'
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -97,9 +91,7 @@ module Toolbars
|
|||
name: 'move',
|
||||
label: I18n.t('experiments.toolbar.move_button'),
|
||||
icon: 'sn-icon sn-icon-move',
|
||||
button_class: 'move-experiments-btn',
|
||||
path: move_modal_experiments_path(id: experiment.id),
|
||||
type: 'remote-modal'
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -114,10 +106,7 @@ module Toolbars
|
|||
name: 'duplicate',
|
||||
label: I18n.t('experiments.toolbar.duplicate_button'),
|
||||
icon: 'sn-icon sn-icon-duplicate',
|
||||
button_class: 'clone-experiment-btn',
|
||||
path: clone_modal_experiments_path(id: experiment.id),
|
||||
type: 'remote-modal',
|
||||
request_method: :get
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -128,10 +117,8 @@ module Toolbars
|
|||
name: 'archive',
|
||||
label: I18n.t('experiments.toolbar.archive_button'),
|
||||
icon: 'sn-icon sn-icon-archive',
|
||||
button_class: 'archive-experiments-btn',
|
||||
path: archive_group_project_experiments_path(project_id: @experiments.first.project_id),
|
||||
type: :request,
|
||||
request_method: :post
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1467,7 +1467,8 @@ en:
|
|||
clone_option: "Duplicate"
|
||||
move_option: "Move"
|
||||
archive_option: "Archive"
|
||||
archive_confirm: "Are you sure you want to archive this project?"
|
||||
archive_confirm_title: "Archive experiment"
|
||||
archive_confirm: "Are you sure you want to archive this experiment?"
|
||||
restore_option: "Restore"
|
||||
more_link: "More"
|
||||
experiment_access: "Access"
|
||||
|
|
|
@ -427,6 +427,8 @@ Rails.application.routes.draw do
|
|||
post :archive_my_modules
|
||||
post :batch_clone_my_modules
|
||||
get :search_tags
|
||||
get :projects_to_clone
|
||||
get :projects_to_move
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue