Merge pull request #6926 from aignatov-bio/ai-sci-9803-replace-reports-table

Replace reports table [SCI-9803]
This commit is contained in:
aignatov-bio 2024-01-11 15:26:52 +01:00 committed by GitHub
commit 37919d8346
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 681 additions and 96 deletions

View file

@ -21,7 +21,18 @@ class ReportsController < ApplicationController
after_action :generate_pdf_report, only: %i(create update generate_pdf)
# Index showing all reports of a single project
def index; end
def index
respond_to do |format|
format.json do
reports = Lists::ReportsService.new(Report.viewable_by_user(current_user, current_team), params).call
render json: reports, each_serializer: Lists::ReportSerializer,
user: current_user, meta: pagination_dict(reports)
end
format.html do
render 'index'
end
end
end
def datatable
render json: ::ReportDatatable.new(
@ -137,7 +148,7 @@ class ReportsController < ApplicationController
# Destroy multiple entries action
def destroy
begin
report_ids = JSON.parse(params[:report_ids])
report_ids = params[:report_ids]
rescue
render_404
end
@ -151,25 +162,11 @@ class ReportsController < ApplicationController
report.destroy
end
redirect_to reports_path
render json: { message: I18n.t('projects.reports.index.modal_delete.success') }
end
def status
docx = @report.docx_file.attached? ? document_preview_report_path(@report, report_type: :docx) : nil
pdf = @report.pdf_file.attached? ? document_preview_report_path(@report, report_type: :pdf) : nil
render json: {
docx: {
processing: @report.docx_processing?,
preview_url: docx,
error: @report.docx_error?
},
pdf: {
processing: @report.pdf_processing?,
preview_url: pdf,
error: @report.pdf_error?
}
}
render json: @report, serializer: Lists::ReportSerializer
end
# Generation actions
@ -283,7 +280,7 @@ class ReportsController < ApplicationController
end
def available_repositories
render json: { results: @available_repositories }, status: :ok
render json: { data: @available_repositories.map { |r| [r.id, r.name] } }
end
def document_preview
@ -302,7 +299,7 @@ class ReportsController < ApplicationController
actions:
Toolbars::ReportsService.new(
current_user,
report_ids: params[:report_ids].split(',')
report_ids: JSON.parse(params[:items]).map { |i| i['id'] }
).actions
}
end
@ -380,7 +377,7 @@ class ReportsController < ApplicationController
end
def save_pdf_params
params.permit(:repository_id, :respository_column_id, :repository_item_id)
params.permit(:repository_id, :repository_column_id, :repository_item_id)
end
def log_activity(type_of, report = @report)

View file

@ -83,15 +83,7 @@ class RepositoryColumnsController < ApplicationController
end
def available_asset_type_columns
if @asset_columns.blank?
render json: {
no_items: t(
'projects.reports.new.save_PDF_to_inventory_modal.no_columns'
)
}
else
render json: { results: @asset_columns }, status: :ok
end
render json: { data: @asset_columns.map { |c| [c.id, c.name] } }, status: :ok
end
def available_columns

View file

@ -275,11 +275,9 @@ class RepositoryRowsController < ApplicationController
"#{link_to(t('projects.reports.new.save_PDF_to_inventory_modal.here'),
repository_path(@repository),
data: { 'no-turbolink' => true })}"
render json: { no_items: no_items_string },
status: :ok
render json: { no_items: no_items_string }, status: :unprocessable_entity
else
render json: { results: load_available_rows },
status: :ok
render json: { results: load_available_rows }, status: :ok
end
end

View file

@ -0,0 +1,10 @@
import { createApp } from 'vue/dist/vue.esm-bundler.js';
import PerfectScrollbar from 'vue3-perfect-scrollbar';
import ReportsTable from '../../vue/reports/table.vue';
import { mountWithTurbolinks } from './helpers/turbolinks.js';
const app = createApp();
app.component('ReportsTable', ReportsTable);
app.config.globalProperties.i18n = window.I18n;
app.use(PerfectScrollbar);
mountWithTurbolinks(app, '#reportsTable');

View file

@ -0,0 +1,158 @@
<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">
{{ i18n.t("projects.reports.new.save_PDF_to_inventory") }}
</h4>
</div>
<div class="modal-body">
<div class="mb-4">{{ i18n.t('projects.reports.new.save_PDF_to_inventory_modal.description_one') }}</div>
<div class="mb-4">{{ i18n.t('projects.reports.new.save_PDF_to_inventory_modal.description_two') }}</div>
<div v-if="false && !report.pdf_file.preview_url" class="mb-4">
<em>{{ i18n.t('projects.reports.new.save_PDF_to_inventory_modal.pdf_not_ready') }}</em>
</div>
<template v-else-if="availableRepositories.length > 0">
<div class="mb-4">
<label class="sci-label">
{{ i18n.t("projects.reports.new.save_PDF_to_inventory_modal.inventory") }}
</label>
<SelectDropdown :optionsUrl="repositoriesUrl" @change="changeRepository" />
</div>
<div v-if="selectedRepository" class="mb-4">
<label class="sci-label">
{{ i18n.t("projects.reports.new.save_PDF_to_inventory_modal.inventory_column") }}
</label>
<SelectDropdown :optionsUrl="columnsUrl"
:urlParams="{repository_id: selectedRepository}"
@change="changeColumn" />
</div>
<div v-if="selectedColumn && !rowsError" class="mb-4">
<label class="sci-label">
{{ i18n.t("projects.reports.new.save_PDF_to_inventory_modal.inventory_item") }}
</label>
<SelectDropdown :options="formattedRowsList" @change="changeRow" />
</div>
<div v-if="rowsError" class="mb-4" v-html="rowsError"></div>
<div
class="text-sn-delete-red"
v-if="fileAlreadyAttached"
v-html="i18n.t('projects.reports.new.save_PDF_to_inventory_modal.asset_present_warning_html')">
</div>
</template>
<div class="mb-4" v-else>
<em>{{ i18n.t('projects.reports.new.save_PDF_to_inventory_modal.no_inventories') }}</em>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button
@click="saveToInventory"
type="button"
class="btn btn-primary"
:disabled="!selectedColumn || !selectedRow">
{{ i18n.t('general.save') }}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import SelectDropdown from '../../shared/select_dropdown.vue';
import axios from '../../../packs/custom_axios.js';
import modalMixin from '../../shared/modal_mixin';
export default {
name: 'NewProtocolModal',
props: {
report: Object,
repositoriesUrl: String,
columnsUrl: String,
rowsUrl: String
},
mixins: [modalMixin],
components: {
SelectDropdown
},
mounted() {
this.getAvailableRepositories();
},
computed: {
formattedRowsList() {
return this.availableRows.map((row) => (
[row.id, row.name]
));
},
fileAlreadyAttached() {
return this.availableRows.find((row) => (
row.id === this.selectedRow
))?.has_file_attached;
}
},
data() {
return {
availableRows: [],
availableRepositories: [],
selectedRepository: null,
selectedColumn: null,
selectedRow: null,
rowsError: null
};
},
methods: {
getAvailableRepositories() {
axios.get(this.repositoriesUrl)
.then((response) => {
this.availableRepositories = response.data.data;
});
},
getAvaialableRows() {
axios.get(this.rowsUrl, {
params: {
repository_id: this.selectedRepository,
repository_column_id: this.selectedColumn
}
})
.then((response) => {
this.availableRows = response.data.results;
})
.catch((error) => {
this.rowsError = error.response.data.no_items;
});
},
changeRepository(repository) {
this.selectedColumn = null;
this.selectedRepository = null;
this.selectedRow = null;
this.$nextTick(() => {
this.selectedRepository = repository;
});
},
changeColumn(column) {
this.selectedColumn = column;
this.selectedRow = null;
this.getAvaialableRows();
},
changeRow(row) {
this.selectedRow = row;
},
saveToInventory() {
axios.post(this.report.urls.save_to_inventory, {
repository_id: this.selectedRepository,
repository_column_id: this.selectedColumn,
repository_item_id: this.selectedRow
})
.then(() => {
this.close();
});
}
}
};
</script>

View file

@ -0,0 +1,73 @@
<template>
<div>
<template v-if="docx.error">
<span class="flex items-center gap-1 text-sn-delete-red">
<i class="fas fa-exclamation-triangle"></i>
{{ i18n.t('projects.reports.index.error') }}
<span v-if="docx.preview_url" class="text-sn-black">|</span>
<a v-if="docx.preview_url" href="#"
class="file-preview-link flex items-center gap-1
docx hover:no-underline whitespace-nowrap"
:data-preview-url="docx.preview_url">
{{ i18n.t('projects.reports.index.previous_docx') }}
</a>
</span>
</template>
<span v-else-if="docx.processing" class="processing docx">
{{ i18n.t('projects.reports.index.generating') }}
</span>
<template v-else-if="docx.preview_url">
<a v-if="docx.preview_url" href="#"
class="file-preview-link flex items-center gap-1
docx hover:no-underline whitespace-nowrap"
:data-preview-url="docx.preview_url">
<i class="sn-icon sn-icon-file-word"></i>
{{ i18n.t('projects.reports.index.docx') }}
</a>
</template>
<a v-else href="#" @click.prevent="generate">
{{ i18n.t('projects.reports.index.generate') }}
</a>
</div>
</template>
<script>
import axios from '../../../packs/custom_axios.js';
export default {
name: 'DocxRenderer',
props: {
params: {
required: true
}
},
data() {
return {
docx: this.params.data.docx_file
};
},
mounted() {
if (this.docx.processing) {
setTimeout(this.checkStatus, 3000);
}
},
methods: {
generate() {
axios.post(this.params.data.urls.generate_docx)
.then(() => {
this.docx.processing = true;
this.checkStatus();
});
},
checkStatus() {
axios.get(this.params.data.urls.status)
.then((response) => {
this.docx = response.data.data.attributes.docx_file;
if (this.docx.processing) {
setTimeout(this.checkStatus, 3000);
}
});
}
}
};
</script>

View file

@ -0,0 +1,73 @@
<template>
<div>
<template v-if="pdf.error">
<span class="flex items-center gap-1 text-sn-delete-red">
<i class="fas fa-exclamation-triangle"></i>
{{ i18n.t('projects.reports.index.error') }}
<span v-if="pdf.preview_url" class="text-sn-black">|</span>
<a v-if="pdf.preview_url" href="#"
class="file-preview-link flex items-center gap-1
pdf hover:no-underline whitespace-nowrap"
:data-preview-url="pdf.preview_url">
{{ i18n.t('projects.reports.index.previous_pdf') }}
</a>
</span>
</template>
<template v-else-if="pdf.preview_url">
<a v-if="pdf.preview_url" href="#"
class="file-preview-link flex items-center gap-1
pdf hover:no-underline whitespace-nowrap"
:data-preview-url="pdf.preview_url">
<i class="sn-icon sn-icon-file-word"></i>
{{ i18n.t('projects.reports.index.pdf') }}
</a>
</template>
<span v-else-if="pdf.processing" class="processing pdf">
{{ i18n.t('projects.reports.index.generating') }}
</span>
<a v-else href="#" @click.prevent="generate">
{{ i18n.t('projects.reports.index.generate') }}
</a>
</div>
</template>
<script>
import axios from '../../../packs/custom_axios.js';
export default {
name: 'DocxRenderer',
props: {
params: {
required: true
}
},
data() {
return {
pdf: this.params.data.pdf_file
};
},
mounted() {
if (this.pdf.processing) {
setTimeout(this.checkStatus, 3000);
}
},
methods: {
generate() {
axios.post(this.params.data.urls.generate_pdf)
.then(() => {
this.pdf.processing = true;
this.checkStatus();
});
},
checkStatus() {
axios.get(this.params.data.urls.status)
.then((response) => {
this.pdf = response.data.data.attributes.pdf_file;
if (this.pdf.processing) {
setTimeout(this.checkStatus, 3000);
}
});
}
}
};
</script>

View file

@ -0,0 +1,181 @@
<template>
<div class="h-full">
<DataTable :columnDefs="columnDefs"
:tableId="'reportTemplates'"
:dataUrl="dataSource"
:reloadingTable="reloadingTable"
:toolbarActions="toolbarActions"
:actionsUrl="actionsUrl"
@tableReloaded="reloadingTable = false"
@update_pdf="updatePdf"
@delete="deleteReport"
@update_docx="updateDocx"
@save_pdf_to_repository="savePdfToRepository"
/>
</div>
<ConfirmationModal
:title="deleteModal.title"
:description="deleteModal.description"
confirmClass="btn btn-danger"
:confirmText="i18n.t('repositories.index.modal_delete.delete')"
ref="deleteModal"
></ConfirmationModal>
<SaveToInventoryModal
v-if="reportToSave"
:report="reportToSave"
:repositoriesUrl="availableRepositoriesUrl"
:columnsUrl="availableColumnsUrl"
:rowsUrl="availableRowsUrl"
ref="saveToInventoryModal"/>
</template>
<script>
/* global HelperModule */
import axios from '../../packs/custom_axios.js';
import DataTable from '../shared/datatable/table.vue';
import DocxRenderer from './renderers/docx.vue';
import PdfRenderer from './renderers/pdf.vue';
import ConfirmationModal from '../shared/confirmation_modal.vue';
import SaveToInventoryModal from './modals/save_to_inventory.vue';
export default {
name: 'ReportsTable',
components: {
DataTable,
DocxRenderer,
PdfRenderer,
ConfirmationModal,
SaveToInventoryModal
},
props: {
dataSource: {
type: String,
required: true
},
actionsUrl: {
type: String,
required: true
},
createUrl: {
type: String
},
availableRepositoriesUrl: {
type: String
},
availableColumnsUrl: {
type: String
},
availableRowsUrl: {
type: String
}
},
data() {
return {
reloadingTable: false,
deleteModal: {
title: '',
description: ''
},
reportToSave: null,
columnDefs: [
{
field: 'project_name',
headerName: this.i18n.t('projects.reports.index.thead_project_name'),
sortable: true
}, {
field: 'name',
headerName: this.i18n.t('projects.reports.index.thead_name'),
sortable: true
}, {
field: 'code',
headerName: this.i18n.t('projects.reports.index.thead_id'),
sortable: true
}, {
field: 'pdf_file',
headerName: this.i18n.t('projects.reports.index.pdf'),
sortable: true,
cellRenderer: 'PdfRenderer'
}, {
field: 'docx_file',
headerName: this.i18n.t('projects.reports.index.docx'),
sortable: true,
cellRenderer: 'DocxRenderer'
}, {
field: 'created_by_name',
headerName: this.i18n.t('projects.reports.index.thead_created_by'),
sortable: true
}, {
field: 'modified_by_name',
headerName: this.i18n.t('projects.reports.index.thead_last_modified_by'),
sortable: true
}, {
field: 'created_at',
headerName: this.i18n.t('projects.reports.index.thead_created_at'),
sortable: true
}, {
field: 'updated_at',
headerName: this.i18n.t('projects.reports.index.thead_updated_at'),
sortable: true
}
]
};
},
computed: {
toolbarActions() {
const left = [];
if (this.createUrl) {
left.push({
name: 'create',
icon: 'sn-icon sn-icon-new-task',
label: this.i18n.t('projects.reports.index.new'),
type: 'link',
path: this.createUrl,
buttonStyle: 'btn btn-primary'
});
}
return {
left,
right: []
};
}
},
methods: {
updateTable() {
this.reloadingTable = true;
},
updatePdf(_event, rows) {
const [report] = rows;
axios.post(report.urls.generate_pdf)
.then(() => {
this.updateTable();
});
},
updateDocx(_event, rows) {
const [report] = rows;
axios.post(report.urls.generate_docx)
.then(() => {
this.updateTable();
});
},
async deleteReport(event, rows) {
this.deleteModal.title = this.i18n.t('projects.reports.index.modal_delete.head_title');
this.deleteModal.description = this.i18n.t('projects.reports.index.modal_delete.message');
const ok = await this.$refs.deleteModal.show();
if (ok) {
axios.post(event.path, { report_ids: rows.map((row) => row.id) }).then((response) => {
this.updateTable();
HelperModule.flashAlertMsg(response.data.message, 'success');
});
}
},
savePdfToRepository(_event, rows) {
const [report] = rows;
this.reportToSave = report;
}
}
};
</script>

View file

@ -2,7 +2,7 @@
<div class="flex py-4 items-center justify-between">
<div class="flex items-center gap-4">
<template v-for="action in toolbarActions.left" :key="action.label">
<a v-if="action.type === 'emit'"
<a v-if="action.type === 'emit' || action.type === 'link'"
:class="action.buttonStyle"
:href="action.path"
@click="doAction(action, $event)">

View file

@ -91,6 +91,7 @@
<script>
import { vOnClickOutside } from '@vueuse/components';
import FixedFlyoutMixin from './mixins/fixed_flyout.js';
import axios from '../../packs/custom_axios.js';
export default {
name: 'SelectDropdown',
@ -110,7 +111,8 @@ export default {
withCheckboxes: { type: Boolean, default: false },
searchable: { type: Boolean, default: false },
clearable: { type: Boolean, default: false },
tagsView: { type: Boolean, default: false }
tagsView: { type: Boolean, default: false },
urlParams: { type: Object, default: () => ({}) }
},
directives: {
'click-outside': vOnClickOutside,
@ -305,10 +307,10 @@ export default {
},
fetchOptions() {
if (this.optionsUrl) {
fetch(`${this.optionsUrl}?query=${this.query || ''}`)
.then((response) => response.json())
.then((data) => {
this.fetchedOptions = data.data;
const params = { query: this.query, ...this.urlParams };
axios.get(this.optionsUrl, { params })
.then((response) => {
this.fetchedOptions = response.data.data;
this.$nextTick(() => {
this.setPosition();
});

View file

@ -0,0 +1,63 @@
# frozen_string_literal: true
module Lists
class ReportSerializer < ActiveModel::Serializer
include Canaid::Helpers::PermissionsHelper
include Rails.application.routes.url_helpers
attributes :name, :code, :project_name, :pdf_file, :docx_file, :created_by_name,
:modified_by_name, :created_at, :updated_at, :urls
def project_name
object['project_name']
end
def created_by_name
object['created_by_name']
end
def modified_by_name
object['modified_by_name']
end
def created_at
I18n.l(object.created_at, format: :full)
end
def updated_at
I18n.l(object.updated_at, format: :full)
end
def archived
object.project.archived?
end
def docx_file
docx = document_preview_report_path(object, report_type: :docx) if object.docx_file.attached?
{
processing: object.docx_processing?,
preview_url: docx,
error: object.docx_error?
}
end
def pdf_file
pdf = document_preview_report_path(object, report_type: :pdf) if object.pdf_file.attached?
{
processing: object.pdf_processing?,
preview_url: pdf,
error: object.pdf_error?
}
end
def urls
{
edit: edit_project_report_path(object.project_id, object.id),
status: status_project_report_path(object.project_id, object.id),
generate_pdf: generate_pdf_project_report_path(object.project_id, object.id),
generate_docx: generate_docx_project_report_path(object.project_id, object.id),
save_to_inventory: save_pdf_to_inventory_item_report_path(object)
}
end
end
end

View file

@ -0,0 +1,68 @@
# frozen_string_literal: true
module Lists
class ReportsService < BaseService
private
def fetch_records
@records = @raw_data.joins(
'LEFT OUTER JOIN users AS creators ' \
'ON reports.user_id = creators.id'
).joins(
'LEFT OUTER JOIN users AS modifiers ' \
'ON reports.last_modified_by_id = modifiers.id'
)
.joins(:project)
.select('reports.* AS reports')
.select('projects.name AS project_name')
.select('creators.full_name AS created_by_name')
.select('modifiers.full_name AS modified_by_name')
end
def filter_records
return if @params[:search].blank?
@records = @records.where_attributes_like(
['reports.name',
'reports.description',
"('RP' || reports.id)",
'projects.name',
'creators.full_name',
'modifiers.full_name'],
@params[:search]
)
end
def sort_records
return unless @params[:order]
case order_params[:column]
when 'docx_file'
@records = @records.left_joins(:docx_file_attachment)
.order(active_storage_attachments: sort_direction(order_params))
.order(docx_file_status: sort_direction(order_params) == 'ASC' ? :desc : :asc)
when 'pdf_file'
@records = @records.left_joins(:pdf_file_attachment)
.order(active_storage_attachments: sort_direction(order_params))
.order(pdf_file_status: sort_direction(order_params) == 'ASC' ? :desc : :asc)
when 'code'
sort_by = "reports.id #{sort_direction(order_params)}"
@records = @records.order(sort_by)
else
sort_by = "#{sortable_columns[order_params[:column].to_sym]} #{sort_direction(order_params)}"
@records = @records.order(sort_by)
end
end
def sortable_columns
@sortable_columns ||= {
name: 'reports.name',
modified_by_name: 'modifiers.full_name',
created_by_name: 'creators.full_name',
project_name: 'projects.name',
created_at: 'reports.created_at',
updated_at: 'reports.updated_at'
}
end
end
end

View file

@ -38,7 +38,7 @@ module ReportActions
raise ReportActions::RepositoryPermissionError, I18n.t('projects.reports.new.no_permissions')
end
@repository_column = @repository.repository_columns.find(@params[:respository_column_id])
@repository_column = @repository.repository_columns.find(@params[:repository_column_id])
@repository_row = @repository.repository_rows.find(@params[:repository_item_id])
end

View file

@ -34,7 +34,6 @@ module Toolbars
name: 'edit',
label: I18n.t('projects.reports.index.edit'),
icon: 'sn-icon sn-icon-edit',
button_id: 'edit-report-btn',
path: edit_project_report_path(@report.project_id, @report.id),
type: :link
}
@ -47,7 +46,7 @@ module Toolbars
name: 'update_pdf',
label: I18n.t('projects.reports.index.update_pdf'),
icon: 'fas fa-file-pdf',
button_id: 'updatePdf'
type: :emit
}
end
@ -55,11 +54,10 @@ module Toolbars
return unless @report && can_manage_report?(@report)
{
name: 'save_pdf_to_inventory',
name: 'save_pdf_to_repository',
label: I18n.t('projects.reports.index.save_pdf_to_inventory'),
icon: 'fas fa-save',
button_id: 'savePdfToInventoryButton',
path: save_pdf_to_inventory_modal_report_path(@report.id)
type: :emit
}
end
@ -70,10 +68,10 @@ module Toolbars
label = @report.docx_file_status == 'docx_empty' ? I18n.t('projects.reports.index.request_docx') : I18n.t('projects.reports.index.update_docx')
{
name: 'generate_docx_action',
name: 'update_docx',
label: label,
icon: 'sn-icon sn-icon-file-word',
button_id: button_id
type: :emit
}
end
@ -84,8 +82,8 @@ module Toolbars
name: 'delete',
label: I18n.t('projects.reports.index.delete'),
icon: 'sn-icon sn-icon-delete',
button_id: 'delete-reports-btn',
type: :link
path: reports_destroy_path,
type: :emit
}
end
end

View file

@ -4,7 +4,4 @@
<%= t('projects.reports.index.head_title') %>
</h1>
</div>
<div id="toolbarWrapper" class="toolbar-row">
<%= render partial: 'reports/index_toolbar' %>
</div>
</div>

View file

@ -10,34 +10,19 @@
<div class="content-pane flexible reports-index">
<%= render partial: 'reports/index_header' %>
<div id="content-reports-index">
<div class="reports-datatable">
<table id="reports-table"
class="table"
data-source="<%= reports_datatable_path(format: :json) %>">
<thead>
<tr>
<th id="select-all">
<div class="sci-checkbox-container">
<input name="select_all" type="checkbox" class="sci-checkbox">
<span class="sci-checkbox-label"></span>
</div>
</th>
<th id="project-name"><%= t('projects.reports.index.thead_project_name') %></th>
<th id="report-name"><%= t('projects.reports.index.thead_name') %></th>
<th id="report-id"><%= t('projects.reports.index.thead_id') %></th>
<th id="report-pdf"><%= t('projects.reports.index.pdf') %></th>
<th id="report-docx"><%= t('projects.reports.index.docx') %></th>
<th id="report-created-by"><%= t('projects.reports.index.thead_created_by') %></th>
<th id="report-last-modified-by"><%= t('projects.reports.index.thead_last_modified_by') %></th>
<th id="report-created-at"><%= t('projects.reports.index.thead_created_at') %></th>
<th id="report-updated-at"><%= t('projects.reports.index.thead_updated_at') %></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div id="reportsTable" class="fixed-content-body">
<reports-table
actions-url="<%= actions_toolbar_reports_url %>"
available-repositories-url="<%= reports_available_repositories_path %>"
available-columns-url="<%= available_asset_type_columns_path %>"
available-rows-url="<%= available_rows_repositories_path %>"
data-source="<%= reports_path(format: :json) %>"
create-url="<%= new_report_path if can_create_reports?(current_team) %>"
/>
</div>
<%= javascript_include_tag 'vue_reports_table' %>
<%= javascript_include_tag 'pdf_js' %>
<%= stylesheet_link_tag 'pdf_js_styles' %>
</div>
<!-- Save report modal -->
@ -61,15 +46,3 @@
</div>
<% end %>
</div>
<div id="actionToolbar" data-behaviour="vue">
<action-toolbar actions-url="<%= actions_toolbar_reports_url %>" />
</div>
<%= render partial: 'reports/modals/regenerate' %>
<%= javascript_include_tag 'reports/save_pdf_to_inventory' %>
<%= javascript_include_tag 'reports/reports_datatable' %>
<%= javascript_include_tag 'pdf_js' %>
<%= stylesheet_link_tag 'pdf_js_styles' %>
<%= javascript_include_tag "vue_components_action_toolbar" %>

View file

@ -718,6 +718,7 @@ en:
head_title: "Delete report/s"
message: "Are you sure to delete selected report/s?"
delete: "Delete"
success: "Report/s successfully deleted."
wizard:
statuses:
step_1: "Select project"

View file

@ -258,9 +258,9 @@ Rails.application.routes.draw do
end
get 'reports/datatable', to: 'reports#datatable'
get 'reports/new_template_values', to: 'reports#new_template_values', defaults: { format: 'json' }
post 'reports/available_repositories', to: 'reports#available_repositories',
get 'reports/available_repositories', to: 'reports#available_repositories',
defaults: { format: 'json' }
post 'available_asset_type_columns',
get 'available_asset_type_columns',
to: 'repository_columns#available_asset_type_columns',
defaults: { format: 'json' }
post 'reports/destroy', to: 'reports#destroy'
@ -762,7 +762,7 @@ Rails.application.routes.draw do
collection do
get :sidebar
post 'available_rows', to: 'repository_rows#available_rows', defaults: { format: 'json' }
get 'available_rows', to: 'repository_rows#available_rows', defaults: { format: 'json' }
get 'export_repository_stock_items_modal'
end

View file

@ -53,7 +53,8 @@ const entryList = {
vue_my_modules_list: './app/javascript/packs/vue/my_modules_list.js',
vue_design_system_select: './app/javascript/packs/vue/design_system/select.js',
vue_protocols_list: './app/javascript/packs/vue/protocols_list.js',
vue_repositories_table: './app/javascript/packs/vue/repositories_table.js'
vue_repositories_table: './app/javascript/packs/vue/repositories_table.js',
vue_reports_table: './app/javascript/packs/vue/reports_table.js'
};
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949