mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-01-04 14:44:26 +08:00
Merge pull request #6926 from aignatov-bio/ai-sci-9803-replace-reports-table
Replace reports table [SCI-9803]
This commit is contained in:
commit
37919d8346
19 changed files with 681 additions and 96 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
10
app/javascript/packs/vue/reports_table.js
Normal file
10
app/javascript/packs/vue/reports_table.js
Normal 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');
|
158
app/javascript/vue/reports/modals/save_to_inventory.vue
Normal file
158
app/javascript/vue/reports/modals/save_to_inventory.vue
Normal 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>
|
73
app/javascript/vue/reports/renderers/docx.vue
Normal file
73
app/javascript/vue/reports/renderers/docx.vue
Normal 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>
|
73
app/javascript/vue/reports/renderers/pdf.vue
Normal file
73
app/javascript/vue/reports/renderers/pdf.vue
Normal 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>
|
181
app/javascript/vue/reports/table.vue
Normal file
181
app/javascript/vue/reports/table.vue
Normal 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>
|
|
@ -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)">
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
63
app/serializers/lists/report_serializer.rb
Normal file
63
app/serializers/lists/report_serializer.rb
Normal 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
|
68
app/services/lists/reports_service.rb
Normal file
68
app/services/lists/reports_service.rb
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" %>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue