mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-09 05:18:01 +08:00
311 lines
11 KiB
Vue
311 lines
11 KiB
Vue
<template>
|
|
<div class="bg-white px-4 my-4 task-section">
|
|
<div class="py-4 flex items-center gap-4">
|
|
<i ref="openHandler"
|
|
@click="toggleContainer"
|
|
class="sn-icon sn-icon-right cursor-pointer">
|
|
</i>
|
|
<div class="flex items-center gap-2">
|
|
<h2 class="my-0 flex items-center gap-1">
|
|
{{ i18n.t('my_modules.details.title') }}
|
|
</h2>
|
|
<GeneralDropdown ref="myModuleDetailsDropdown">
|
|
<template v-slot:field>
|
|
<button class="btn btn-light btn-black icon-btn">
|
|
<i class="sn-icon sn-icon-info"></i>
|
|
</button>
|
|
</template>
|
|
<template v-slot:flyout>
|
|
<div class="grid grid-cols-[auto_1fr] gap-y-2 gap-x-4 py-2.5 max-h-[inherit] w-[400px] p-4">
|
|
<div class="font-bold text-sm">
|
|
{{ i18n.t('my_modules.details.info_popover.project_label') }}
|
|
</div>
|
|
<div class="text-sm truncate" :title="myModule.attributes.project_name">
|
|
{{ myModule.attributes.project_name }}
|
|
</div>
|
|
<div class="font-bold text-sm">
|
|
{{ i18n.t('my_modules.details.info_popover.experiment_label') }}
|
|
</div>
|
|
<div class="text-sm truncate" :title="myModule.attributes.experiment_name">
|
|
{{ myModule.attributes.experiment_name }}
|
|
</div>
|
|
<div class="font-bold text-sm">
|
|
{{ i18n.t('my_modules.details.info_popover.creator_label') }}
|
|
</div>
|
|
<div class="text-sm truncate" :title="myModule.attributes.created_by_name">
|
|
{{ myModule.attributes.created_by_name }}
|
|
<span v-if="myModule.attributes.is_creator_current_user">
|
|
({{ i18n.t('my_modules.details.info_popover.creator_same_user_label') }})
|
|
</span>
|
|
</div>
|
|
<div class="font-bold text-sm">
|
|
{{ i18n.t('my_modules.details.info_popover.created_label') }}
|
|
</div>
|
|
<div class="text-sm truncate">
|
|
{{ myModule.attributes.created_at }}
|
|
</div>
|
|
<div class="font-bold text-sm">
|
|
{{ i18n.t('my_modules.details.info_popover.modified_label') }}
|
|
</div>
|
|
<div class="text-sm truncate" :title="myModule.attributes.last_modified_by_name">
|
|
<span v-if="myModule.attributes.last_modified_by_name">
|
|
{{ i18n.t('my_modules.details.info_popover.modified_value',
|
|
{
|
|
date: myModule.attributes.updated_at,
|
|
full_name: myModule.attributes.last_modified_by_name
|
|
}) }}
|
|
</span>
|
|
<span v-else class="truncate">
|
|
{{ i18n.t('my_modules.details.info_popover.modified_value_without_user',
|
|
{
|
|
date: myModule.attributes.updated_at
|
|
}) }}
|
|
</span>
|
|
</div>
|
|
<div class="col-span-2 text-sm">
|
|
<a href="#" @click.prevent="openAccessModal">
|
|
{{ i18n.t('my_modules.details.info_popover.view_task_access') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</GeneralDropdown>
|
|
<span>
|
|
{{ myModule.attributes.code }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div ref="detailsContainer" class="overflow-hidden flex flex-col gap-1 transition-all pr-4 pl-9" style="max-height: 0px;">
|
|
<div class="flex items-center">
|
|
<span class="sn-icon sn-icon-calendar"></span>
|
|
<span class="tw-hidden lg:block ml-2">
|
|
{{ i18n.t('my_modules.details.start_date') }}
|
|
</span>
|
|
<div class="w-48 font-bold">
|
|
<DateTimePicker
|
|
v-if="myModule.attributes.permissions.manage_due_date"
|
|
@change="setDueDate"
|
|
:defaultValue="startDate"
|
|
mode="datetime"
|
|
size="mb"
|
|
:noBorder="true"
|
|
:noIcons="true"
|
|
valueType="stringWithoutTimezone"
|
|
:placeholder="i18n.t('my_modules.details.no_start_date_placeholder')"
|
|
:clearable="true"
|
|
/>
|
|
<span v-else class="font-bold ml-2">
|
|
{{ myModule.attributes.start_date_cell.value_formatted || i18n.t('my_modules.details.no_start_date_placeholder') }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<span class="sn-icon sn-icon-calendar"></span>
|
|
<span class="tw-hidden lg:block ml-2">
|
|
{{ i18n.t('my_modules.details.due_date') }}
|
|
</span>
|
|
<div class="w-48 font-bold">
|
|
<DateTimePicker
|
|
v-if="myModule.attributes.permissions.manage_due_date"
|
|
@change="setDueDate"
|
|
mode="datetime"
|
|
:defaultValue="dueDate"
|
|
size="mb"
|
|
:noBorder="true"
|
|
:noIcons="true"
|
|
valueType="stringWithoutTimezone"
|
|
:placeholder="i18n.t('my_modules.details.no_due_date_placeholder')"
|
|
:clearable="true"
|
|
/>
|
|
<span v-else class="font-bold ml-2">
|
|
{{ myModule.attributes.due_date_cell.value_formatted || i18n.t('my_modules.details.no_due_date_placeholder') }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div v-if="myModule.attributes.completed_on" class="flex items-center gap-2 h-10">
|
|
<span class="sn-icon sn-icon-calendar"></span>
|
|
<span class="tw-hidden lg:block">
|
|
{{ i18n.t('my_modules.details.completed_date') }}
|
|
</span>
|
|
</div>
|
|
<div class="flex gap-2 mt-2.5">
|
|
<span class="sn-icon sn-icon-users"></span>
|
|
<span class="tw-hidden lg:block shrink-0">
|
|
{{ i18n.t('my_modules.details.assigned_users') }}
|
|
</span>
|
|
<div class="grow -mt-2.5">
|
|
<SelectDropdown
|
|
@change="setUsers"
|
|
:options="formattedUsers"
|
|
:option-renderer="usersRenderer"
|
|
:label-renderer="usersRenderer"
|
|
:multiple="true"
|
|
class="grow"
|
|
:value="users"
|
|
:searchable="true"
|
|
:placeholder="i18n.t('experiments.canvas.new_my_module_modal.assigned_users_placeholder')"
|
|
:tagsView="true">
|
|
</SelectDropdown>
|
|
</div>
|
|
</div>
|
|
<div class="flex gap-2 mb-6 mt-2.5">
|
|
<span class="sn-icon sn-icon-tag"></span>
|
|
<span class="tw-hidden lg:block shrink-0">
|
|
{{ i18n.t('my_modules.details.tags') }}
|
|
</span>
|
|
<div class="grow -mt-1.5">
|
|
<TagsInput :subject="myModule" v-if="myModule" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import GeneralDropdown from '../shared/general_dropdown.vue';
|
|
import DateTimePicker from '../shared/date_time_picker.vue';
|
|
import SelectDropdown from '../shared/select_dropdown.vue';
|
|
import TagsInput from '../shared/tags_input.vue';
|
|
import axios from '../../packs/custom_axios.js';
|
|
import escapeHtml from '../shared/escape_html.js';
|
|
import {
|
|
my_module_path,
|
|
assigned_users_my_module_path,
|
|
designate_users_my_module_user_my_modules_path
|
|
} from '../../routes.js';
|
|
|
|
export default {
|
|
name: 'MyModuleDetails',
|
|
props: {
|
|
myModule: {
|
|
type: Object,
|
|
required: true
|
|
}
|
|
},
|
|
components: {
|
|
GeneralDropdown,
|
|
DateTimePicker,
|
|
SelectDropdown,
|
|
TagsInput
|
|
},
|
|
data() {
|
|
return {
|
|
sectionOpened: false,
|
|
allUsers: [],
|
|
users: [],
|
|
startDate: null,
|
|
dueDate: null,
|
|
};
|
|
},
|
|
mixins: [escapeHtml],
|
|
computed: {
|
|
formattedUsers() {
|
|
return this.allUsers.map((user) => (
|
|
[
|
|
parseInt(user.id, 10),
|
|
user.attributes.name,
|
|
user.attributes.avatar_url
|
|
]
|
|
));
|
|
}
|
|
},
|
|
created() {
|
|
if (this.myModule.attributes.start_date_cell.value) {
|
|
this.startDate = new Date(this.myModule.attributes.start_date_cell.value);
|
|
}
|
|
if (this.myModule.attributes.due_date_cell.value) {
|
|
this.dueDate = new Date(this.myModule.attributes.due_date_cell.value);
|
|
}
|
|
},
|
|
mounted() {
|
|
this.loadUsers();
|
|
},
|
|
methods: {
|
|
updateUrl() {
|
|
return my_module_path(this.myModule.id);
|
|
},
|
|
recalculateContainerSize(offset = 0) {
|
|
const container = this.$refs.detailsContainer;
|
|
const handler = this.$refs.openHandler;
|
|
|
|
if (this.sectionOpened) {
|
|
container.style.maxHeight = `${container.scrollHeight + offset}px`;
|
|
handler.classList.remove('sn-icon-right');
|
|
handler.classList.add('sn-icon-down');
|
|
} else {
|
|
container.style.maxHeight = '0px';
|
|
handler.classList.remove('sn-icon-down');
|
|
handler.classList.add('sn-icon-right');
|
|
}
|
|
},
|
|
toggleContainer() {
|
|
this.sectionOpened = !this.sectionOpened;
|
|
this.recalculateContainerSize(60);
|
|
},
|
|
reloadMyModule() {
|
|
this.$emit('reloadMyModule');
|
|
},
|
|
openAccessModal() {
|
|
// Legacy part - to be updated
|
|
const container = document.getElementById('accessModalContainer');
|
|
console.log(this.$refs.myModuleDetailsDropdown);
|
|
this.$refs.myModuleDetailsDropdown.isOpen = false;
|
|
$.get(container.dataset.url, (data) => {
|
|
const object = {
|
|
...data.data.attributes,
|
|
id: data.data.id,
|
|
type: data.data.type
|
|
};
|
|
const { rolesUrl } = container.dataset;
|
|
const params = {
|
|
object: object,
|
|
roles_path: rolesUrl
|
|
};
|
|
const modal = $('#accessModalComponent').data('accessModal');
|
|
modal.params = params;
|
|
modal.open();
|
|
});
|
|
},
|
|
loadUsers() {
|
|
const usersUrl = assigned_users_my_module_path(this.myModule.id);
|
|
axios.get(usersUrl).then((response) => {
|
|
this.allUsers = response.data.data;
|
|
this.users = this.myModule.attributes.designated_user_ids || [];
|
|
});
|
|
},
|
|
usersRenderer(user) {
|
|
return `<div class="flex items-center gap-2 truncate">
|
|
<img class="w-6 h-6 rounded-full" src="${user[2]}">
|
|
<span title="${escapeHtml(user[1])}" class="truncate">${escapeHtml(user[1])}</span>
|
|
</div>`;
|
|
},
|
|
setDueDate(value) {
|
|
const updateUrl = my_module_path(this.myModule.id);
|
|
axios.put(updateUrl, {
|
|
my_module: {
|
|
due_date: value,
|
|
}
|
|
}).then(() => {
|
|
this.reloadMyModule();
|
|
});
|
|
},
|
|
setStartDate(value) {
|
|
const updateUrl = my_module_path(this.myModule.id);
|
|
axios.put(updateUrl, {
|
|
my_module: {
|
|
started_on: value,
|
|
}
|
|
}).then(() => {
|
|
this.reloadMyModule();
|
|
});
|
|
},
|
|
setUsers(value) {
|
|
axios.post(designate_users_my_module_user_my_modules_path({ my_module_id: this.myModule.id }), {
|
|
user_ids: value
|
|
}).then(() => {
|
|
this.reloadMyModule();
|
|
})
|
|
}
|
|
}
|
|
};
|
|
</script>
|