Merge pull request #8261 from aignatov-bio/ai-sci-11562-refactor-item-consumption

Refactor consumption modal [SCI-11562]
This commit is contained in:
aignatov-bio 2025-02-27 13:19:39 +01:00 committed by GitHub
commit c0a8088ce6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 264 additions and 227 deletions

View file

@ -223,11 +223,7 @@ class MyModuleRepositoriesController < ApplicationController
@repository_row = @repository.repository_rows.find(params[:row_id])
@module_repository_row = @my_module.my_module_repository_rows.find_by(repository_row: @repository_row)
@stock_value = @module_repository_row.repository_row.repository_stock_value
render json: {
html: render_to_string(
partial: 'my_modules/repositories/consume_stock_modal_content'
)
}
render 'my_modules/repositories/consume_modal'
end
def update_consumption

View file

@ -0,0 +1,124 @@
<template>
<div ref="modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content" v-if="consumeData">
<div class="modal-header">
<button type="button" class="close self-start" data-dismiss="modal" aria-label="<%= t('general.close') %>">
<i class="sn-icon sn-icon-close"></i>
</button>
<h4 class="modal-title">
<span v-if="consumeData.consumed_stock">{{ i18n.t('my_modules.repository.stock_modal.title_edit', { name: consumeData.name }) }}</span>
<span v-else>{{ i18n.t('my_modules.repository.stock_modal.title', { name: consumeData.name }) }}</span>
</h4>
</div>
<div class="modal-body">
<p class="mb-6">{{ i18n.t('my_modules.repository.stock_modal.description') }}</p>
<div class="mb-6">
<label>{{ i18n.t('my_modules.repository.stock_modal.amount') }}</label>
<div class="sci-input-container-v2 flex"
:class="{'error': newConsume.consume < 0}"
:data-error-text="i18n.t('repository_stock_values.manage_modal.amount_error')">
<input type="text"
class="sci-input-field !w-32"
:value="newConsume.consume"
@input="changeConsume"
:placeholder="i18n.t('my_modules.repository.stock_modal.consumed')"
tabindex="1" />
<span class="units relative left-32 ml-1">{{ consumeData.unit }}</span>
</div>
</div>
<div class="items-center grid grid-cols-[1fr,auto,1fr] gap-2 mb-6">
<div class="py-2 bg-sn-super-light-grey flex rounder items-center flex-col gap-2" :class="{'text-sn-alert-passion': consumeData.initial_stock < 0}">
<span class="text-xs text-sn-grey-500">{{ i18n.t('repository_stock_values.manage_modal.current_stock') }}</span>
<h1 class="my-0">{{ consumeData.formatted_stock }}</h1>
<span class="text-xs">{{ consumeData.unit }}</span>
</div>
<div class="p-4">
<i class="sn-icon sn-icon-arrow-right"></i>
</div>
<div class="py-2 bg-sn-super-light-grey px-2 bg-sn-super-light-grey flex rounder items-center flex-col gap-2"
:class="{'text-sn-alert-passion': finalStock < 0}"
>
<span class="text-sm text-sn-grey-500">{{ i18n.t('repository_stock_values.manage_modal.new_stock') }}</span>
<h1 class="my-0">{{ finalStock || '-' }}</h1>
<span class="text-xs">{{ consumeData.unit }}</span>
</div>
</div>
<label>{{ i18n.t('my_modules.repository.stock_modal.comment') }}</label>
<div class="sci-input-container-v2 comments-container" data-error-text="<%= t('repository_stock_values.manage_modal.comment_limit') %>">
<input type="text" class="sci-input-field"
v-model="newConsume.comment" :placeholder="i18n.t('my_modules.repository.stock_modal.enter_comment')" tabindex="1" />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
<button type="button" class="btn btn-primary"
@click="$emit('updateConsume', {newConsume: newConsume, finalStock: finalStock})"
:disabled="!validConsume">{{ i18n.t('general.save') }}</button>
</div>
</div>
</div>
</div>
</template>
<script>
/* global Decimal formatDecimalValue */
import axios from '../../../../packs/custom_axios.js';
import modalMixin from '../../../shared/modal_mixin';
export default {
name: 'EditModal',
props: {
row: Object
},
data() {
return {
consumeData: null,
initialValue: null,
initialStock: null,
finalStock: null,
newConsume: {
consume: null,
comment: null,
unit: null,
url: null
}
};
},
created() {
this.getConsumeData();
},
mixins: [modalMixin],
computed: {
validConsume() {
return this.newConsume.consume > 0;
}
},
methods: {
getConsumeData() {
axios.get(this.row.consumedStock.updateStockConsumptionUrl)
.then((response) => {
this.consumeData = response.data;
if (this.consumeData.formatted_stock_consumption) {
this.newConsume.consume = new Decimal(this.consumeData.formatted_stock_consumption);
}
this.newConsume.unit = this.consumeData.unit;
this.newConsume.url = this.consumeData.update_url;
this.initialStock = new Decimal(this.consumeData.initial_stock);
this.initialValue = this.newConsume.consume || new Decimal(0);
});
},
changeConsume(e) {
const { value } = e.target;
this.newConsume.consume = formatDecimalValue(String(value), this.row.decimals);
this.finalStock = this.initialValue.minus(new Decimal(value || 0)).plus(this.initialStock);
if (e.target.value.length === 0) {
e.target.parentNode.dataset.errorText = this.i18n.t('repository_stock_values.manage_modal.amount_error');
} else if (e.target.value <= 0) {
e.target.parentNode.dataset.errorText = this.i18n.t('repository_stock_values.manage_modal.negative_error');
}
}
}
};
</script>

View file

@ -0,0 +1,40 @@
<template>
<div class="flex items-center gap-1">
<span v-if="!data.stock_present && data.value && data.value.consumed_stock !== null">
{{ data.value.consumed_stock_formatted }}
</span>
<span v-else-if="!data.stock_present"> - </span>
<span v-else-if="!data.consumptionManagable && data.value && !data.value.consumed_stock" class="text-sn-grey-500">
{{ i18n.t('libraries.manange_modal_column.stock_type.stock_consumption_locked') }}
</span>
<span v-else-if="!data.consumptionPermitted || !data.consumptionManagable">
{{ data.value.consumed_stock_formatted }}
</span>
<span v-else-if="!data.value.consumed_stock"
class="text-sn-grey-700 cursor-pointer"
@click="params.dtComponent.consume(this.params.data)"
>
<i class="sn-icon sn-icon-test-tube"></i>
{{ i18n.t('libraries.manange_modal_column.stock_type.add_stock_consumption') }}
</span>
<span v-else class="cursor-pointer text-sn-blue" @click="params.dtComponent.consume(this.params.data)">
{{ data.value.consumed_stock_formatted }}
</span>
</div>
</template>
<script>
export default {
props: {
params: {
type: Object,
required: true
}
},
computed: {
data() {
return this.params.data.consumedStock;
}
}
};
</script>

View file

@ -0,0 +1,25 @@
<template>
<div class="flex items-center gap-1">
<i v-if="params.data.stock.stock_status"
:class="{
'text-sn-alert-passion': params.data.stock.stock_status === 'empty',
'text-sn-alert-brittlebush': params.data.stock.stock_status === 'low'
}"
class="sn-icon sn-icon-alert-warning shrink-0"></i>
<span v-if="params.data.stock.value" class="truncate">
{{ params.data.stock.value.stock_formatted }}
</span>
<span v-else class="text-sn-grey-500">{{ i18n.t('libraries.manange_modal_column.stock_type.no_item_stock') }}</span>
</div>
</template>
<script>
export default {
props: {
params: {
type: Object,
required: true
}
}
};
</script>

View file

@ -41,6 +41,14 @@
</div>
</div>
</div>
<ConsumeModal v-if="openConsumeModal" @updateConsume="updateConsume" @close="openConsumeModal = false" :row="selectedRow" />
<ConfirmationModal
:title="i18n.t('my_modules.repository.stock_warning_modal.title')"
:description="warningModalDescription"
confirmClass="btn btn-primary"
:confirmText="i18n.t('my_modules.repository.stock_warning_modal.consume_anyway')"
ref="warningModal"
></ConfirmationModal>
</div>
</template>
<script>
@ -48,7 +56,11 @@ import { AgGridVue } from 'ag-grid-vue3';
import axios from '../../../packs/custom_axios.js';
import CustomHeader from '../../shared/datatable/tableHeader';
import Pagination from '../../shared/datatable/pagination.vue';
import nameRenderer from './renderers/name.vue';
import NameRenderer from './renderers/name.vue';
import StockRenderer from './renderers/stock.vue';
import ConsumeRenderer from './renderers/consume.vue';
import ConsumeModal from './modals/consume.vue';
import ConfirmationModal from '../../shared/confirmation_modal.vue';
export default {
name: 'AssignedRepository',
@ -59,7 +71,11 @@ export default {
AgGridVue,
agColumnHeader: CustomHeader,
Pagination,
nameRenderer
NameRenderer,
StockRenderer,
ConsumeRenderer,
ConsumeModal,
ConfirmationModal
},
data: () => ({
assignedItems: {
@ -72,10 +88,12 @@ export default {
perPage: 20,
gridApi: null,
columnApi: null,
gridReady: false
gridReady: false,
openConsumeModal: false,
selectedRow: null,
warningModalDescription: '',
submitting: false
}),
created() {
},
computed: {
preparedAssignedItems() {
return this.assignedItems.data;
@ -86,7 +104,7 @@ export default {
flex: 1,
headerName: this.i18n.t('repositories.table.row_name'),
sortable: true,
cellRenderer: 'nameRenderer',
cellRenderer: 'NameRenderer',
comparator: () => null
}];
@ -95,13 +113,18 @@ export default {
field: 'stock',
headerName: this.repository.attributes.stock_column_name,
sortable: true,
cellRenderer: 'StockRenderer',
comparator: () => null
});
columns.push({
field: 'consumedStock',
headerName: this.i18n.t('repositories.table.row_consumption'),
sortable: true,
comparator: () => null
comparator: () => null,
cellRendererParams: {
dtComponent: this
},
cellRenderer: 'ConsumeRenderer'
});
}
return columns;
@ -174,6 +197,40 @@ export default {
setPage(page) {
this.page = page;
this.getRows();
},
consume(row) {
this.selectedRow = row;
this.openConsumeModal = true;
},
async updateConsume({ newConsume, finalStock }) {
this.openConsumeModal = false;
const {
consume, comment, url, unit
} = newConsume;
let readyToUpdate = false;
if (finalStock < 0) {
this.warningModalDescription = this.i18n.t('my_modules.repository.stock_warning_modal.description_html', { value: `${consume} ${unit}` });
const ok = await this.$refs.warningModal.show();
if (ok) {
readyToUpdate = true;
}
} else {
readyToUpdate = true;
}
if (readyToUpdate) {
if (this.submitting) return;
this.submitting = true;
axios.post(url, {
stock_consumption: consume,
comment
}).then(() => {
this.getRows();
this.submitting = false;
});
}
}
}
};

View file

@ -88,35 +88,6 @@
my-module-id="<%= @my_module.id%>"
/>
</div>
<!-- Assigned items -->
<div class="task-section hidden">
<div class="task-section-header">
<a class="task-section-caret" role="button" data-toggle="collapse" href="#assigned-items-container" aria-expanded="true" aria-controls="assigned-items-container">
<i class="sn-icon sn-icon-right"></i>
<span class="task-section-title ">
<h2 class="assigned-items-title" data-assigned-items-count="<%= @assigned_repositories.map(&:assigned_rows_count).sum %>">
<%= t('my_modules.assigned_items.title') %>
</h2>
</span>
</a>
<div class="actions-block">
<% if can_assign_my_module_repository_rows?(@my_module) %>
<div class="dropdown repositories-assign-container" data-repositories-url="<%= my_module_repositories_dropdown_list_path(@my_module) %>">
<a href="#" id="repositories-assign-button" class="btn btn-light btn-block" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span><%= t('my_modules.assigned_items.assign_from') %></span>
<span class="sn-icon sn-icon-down"></span>
</a>
<ul class="dropdown-menu repositories-dropdown-menu perfect-scrollbar rounded !p-2.5 sn-shadow-menu-sm flex flex-col gap-[1px]" aria-labelledby="repositories-assign-button">
</ul>
</div>
<% end %>
</div>
</div>
<div class="collapse in panel-group task-section-body" id="assigned-items-container" aria-expanded="true"
data-repositories-list-url="<%= my_module_repositories_list_html_path(@my_module) %>">
<%= render partial: "my_modules/repositories/repositories_list" %>
</div>
<%= render partial: "my_modules/repositories/full_view_modal" %>
</div>
<!-- Protocol -->
@ -162,9 +133,6 @@
<!-- Delete file modal -->
<%= render partial: 'assets/asset_delete_modal' %>
<!-- Consume Stock Modal -->
<%= render partial: 'my_modules/repositories/consume_stock_modal'%>
<!-- Tags modal -->
<div id="tagsModalContainer" class="vue-tags-modal">
<div ref="tagsModal" id="tagsModalComponent"></div>

View file

@ -1,39 +0,0 @@
<div class="modal text-sm"
id="consumeRepositoryStockValueModal"
tabindex="-1"
role="dialog"
aria-labelledby="consumeRepositoryStockValueLabel">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
</div>
</div>
</div>
<div class="modal text-sm"
id="consumeRepositoryStockValueModalWarning"
tabindex="-1"
role="dialog">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close self-start" data-dismiss="modal" aria-label="<%= t('general.close') %>">
<i class="sn-icon sn-icon-close"></i>
</button>
<h4 class="modal-title">
<%= t('my_modules.repository.stock_warning_modal.title') %>
</h4>
</div>
<div class="modal-body">
<p></p>
<div class="modal-footer">
<button type="button"
id="cancel"
class="btn btn-secondary cancel-consumption"><%=t('general.cancel') %></button>
<%= submit_tag t('my_modules.repository.stock_warning_modal.consume_anyway'), class: "btn btn-primary confirm-consumption-button"%>
</div>
</div>
</div>
</div>
<%= javascript_include_tag 'my_modules/stock' %>

View file

@ -1,65 +0,0 @@
<%= form_with url: update_consumption_my_module_repository_path(@my_module, @repository, module_row_id: @module_repository_row),
method: :post,
html: { data: { remote: true } } do |f| %>
<div class="modal-header">
<button type="button" class="close self-start" data-dismiss="modal" aria-label="<%= t('general.close') %>">
<i class="sn-icon sn-icon-close"></i>
</button>
<h4 class="modal-title">
<% if @module_repository_row.stock_consumption %>
<%= t('my_modules.repository.stock_modal.title_edit', name: @repository_row.name)%>
<% else %>
<%= t('my_modules.repository.stock_modal.title', name: @repository_row.name)%>
<% end %>
</h4>
</div>
<div class="modal-body">
<p><%= t('my_modules.repository.stock_modal.description') %></p>
<div class="consumption-container">
<div class="sci-input-container" data-error-text="<%= t('repository_stock_values.manage_modal.amount_error') %>">
<%= f.label :stock_consumption, t('my_modules.repository.stock_modal.amount') %>
<%= f.text_field :stock_consumption,
value: @module_repository_row.formated_stock_consumption,
tabindex: 1,
placeholder: t('my_modules.repository.stock_modal.consumed') ,
class: 'sci-input-field',
data: {initial_value: (@module_repository_row.stock_consumption || 0),
initial_stock: @stock_value.amount,
decimals: @stock_value.repository_cell.repository_column.metadata['decimals'] } %>
</div>
<span class="units"> <%= @stock_value.repository_stock_unit_item&.data %></span>
</div>
<div class="row">
<div class="col-sm-12">
<div class="stock-update-view">
<div class="stock-initial-container bg-sn-super-light-grey <%= 'negative' if @stock_value.amount < 0 %>">
<span class="subtitle"><%= t('repository_stock_values.manage_modal.current_stock') %></span>
<span class="value"><%= @stock_value.formatted_value %></span>
<span class="units"><%= @stock_value.repository_stock_unit_item&.data %></span>
</div>
<div class="stock-arrow">
<i class="sn-icon sn-icon-arrow-right"></i>
</div>
<div class="stock-final-container bg-sn-super-light-grey">
<span class="subtitle"><%= t('repository_stock_values.manage_modal.new_stock') %></span>
<span class="value">-</span>
<span class="units"><%= @stock_value.repository_stock_unit_item&.data %></span>
</div>
</div>
</div>
</div>
<div class="sci-input-container comments-container mt-3" data-error-text="<%= t('repository_stock_values.manage_modal.comment_limit') %>">
<%= f.label :comment, t('my_modules.repository.stock_modal.comment') %>
<%= f.text_field :comment, placeholder: t('my_modules.repository.stock_modal.enter_comment'), tabindex: 1, class: 'sci-input-field' %>
</div>
</div>
<div class="modal-footer">
<button type="button"
id="cancel"
class="btn btn-secondary"
tabindex="4"
data-dismiss="modal"><%=t('general.cancel') %></button>
<%= submit_tag t('general.save'), class: "btn btn-primary update-consumption-button", disabled: @module_repository_row.stock_consumption.nil?, tabindex: 1 %>
</div>
<% end %>

View file

@ -1,22 +0,0 @@
<% @repositories.each do |repository| %>
<li class="repository"
data-table-url="<%= full_view_table_my_module_repository_path(@my_module, repository) %>"
data-assign-url-modal="<%= assign_modal_my_module_repository_path(@my_module, repository) %>"
data-update-url-modal="<%= update_modal_my_module_repository_path(@my_module, repository) %>" data-repository-id="<%= repository.id %>"
data-rows-count="<%= repository.rows_count %>" >
<span class="!px-3 !py-2.5 rounded hover:!bg-sn-super-light-grey !text-sn-blue block cursor-pointer">
<% if repository.shared_with?(current_team) %>
<span class="shared-icon">
<i class="sn-icon sn-icon sn-icon-users"></i>
</span>
<% end %>
<span class="name" data-rows-count="<%= repository.rows_count %>"><%= repository.name %></span>
<% if repository.rows_count.positive? %>
<span class="assigned-items">
<i class="fas fa-file-signature"></i>
<%= repository.rows_count %>
</span>
<% end %>
</span>
</li>
<% end %>

View file

@ -1,57 +0,0 @@
<% @assigned_repositories.each do |repository| %>
<div class="assigned-repository panel" data-repository-id="<%= repository.id %>">
<a class="assigned-repository-caret collapsed"
role="button"
data-toggle="collapse"
href="#assigned-repository-items-container-<%= repository.id %>"
data-parent="#assigned-items-container"
>
<i class="sn-icon sn-icon-right"></i>
<span class="assigned-repository-title" data-rows-count="<%= repository.assigned_rows_count %>">
<%= repository.name %>
</span>
<% if repository.is_a?(RepositorySnapshot) %>
<span class="snapshot-tag">
<%= t('my_modules.repository.snapshots.simple_view.snapshot_tag') %>
</span>
<% end %>
<div class="action-buttons">
<button class="btn btn-light icon-btn full-screen" data-table-url="<%= assigned_repository_full_view_table_path(@my_module, repository) %><%= '?include_stock_consumption=true' if repository.has_stock_consumption? %>">
<i class="sn-icon sn-icon-expand"></i>
</button>
</div>
</a>
<div class="collapse assigned-repository-container"
id="assigned-repository-items-container-<%= repository.id %>"
data-repository-url="<%= assigned_repository_simple_view_index_path(@my_module, repository) %>"
data-footer-label="<%= assigned_repository_simple_view_footer_label(repository) %>"
data-name-column-id="<%= assigned_repository_simple_view_name_column_id(repository) %>"
>
<table class="table hidden repository-table repository-dataTable"
data-stock-management="<%= repository.has_stock_management? && repository.has_stock_consumption? %>"
data-stock-consumption-editable="<%= can_update_my_module_stock_consumption?(@my_module) && repository.has_stock_consumption? %>">
<thead>
<tr>
<th class="row-name"><%= t("repositories.table.row_name") %></th>
<% if repository.has_stock_management? && repository.has_stock_consumption? %>
<th class="row-stock" data-columns-visible="false"><%= repository.repository_stock_column.name %></th>
<th class="row-consumption" data-columns-visible="false"><%= t("repositories.table.row_consumption") %></th>
<% end %>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<%= render 'shared/dialog',
id: "snapshot-error-#{repository.id}",
type: "error",
shown: false,
title: t("my_modules.modals.snapshot_error.title"),
body:
t(
"my_modules.modals.snapshot_error.body_html",
repository: repository.name
)
%>
</div>
<% end %>

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
json.stock_consumption @module_repository_row.stock_consumption
json.name @repository_row.name
json.unit @stock_value.repository_stock_unit_item&.data
json.formatted_stock_consumption @module_repository_row.formated_stock_consumption
json.decimals @stock_value.repository_cell.repository_column.metadata['decimals']
json.initial_stock @stock_value.amount
json.formatted_stock @stock_value.formatted_value
json.update_url update_consumption_my_module_repository_path(@my_module, @repository, module_row_id: @module_repository_row)