mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-12-08 20:19:30 +00:00
Merge branch 'master' into label_printing_on_A4
This commit is contained in:
commit
248bc82eb5
521 changed files with 59933 additions and 17536 deletions
|
|
@ -1,4 +1,5 @@
|
|||
{% import "helper.twig" as helper %}
|
||||
{% import "vars.macro.twig" as vars %}
|
||||
{% import "components/search.macro.html.twig" as search %}
|
||||
|
||||
<nav class="navbar navbar-expand-md bg-body-tertiary border-bottom shadow-sm fixed-top py-0" id="navbar">
|
||||
|
|
@ -17,7 +18,7 @@
|
|||
</div>
|
||||
|
||||
<a class="navbar-brand" href="{{ path('homepage') }}"><i class="fa fa-microchip"
|
||||
aria-hidden="true"></i> {{ partdb_title }}</a>
|
||||
aria-hidden="true"></i> {{ vars.partdb_title() }}</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent"
|
||||
aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
|
||||
<div class="nav flex-column">
|
||||
{% for item in sidebar_items %}
|
||||
{{ tree.treeview_sidebar('sidebar-panel-' ~ loop.index, item) }}
|
||||
{% for item in settings_instance('sidebar').items %}
|
||||
{{ tree.treeview_sidebar('sidebar-panel-' ~ loop.index, item.value) }}
|
||||
<div class="mb-2"></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
@ -22,9 +22,14 @@
|
|||
<div class="d-none" data-title="{{ current_page_title|trim|raw }}" {{ stimulus_controller('turbo/title') }}></div>
|
||||
|
||||
<div class="d-none" {{ stimulus_controller('turbo/locale_menu') }}>
|
||||
{% for locale in locale_menu %}
|
||||
{% set locales = settings_instance('localization').languageMenuEntries %}
|
||||
{% if locales is empty %}
|
||||
{% set locales = locale_menu %}
|
||||
{% endif %}
|
||||
|
||||
{% for locale in locales %}
|
||||
<a class="dropdown-item" data-turbo="false" data-turbo-frame="_top" href="{{ path(app.request.attributes.get('_route'),
|
||||
app.request.query.all|merge(app.request.attributes.get('_route_params'))|merge({'_locale': locale})) }}">
|
||||
{{ locale|language_name }} ({{ locale|upper }})</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@
|
|||
<li class="nav-item">
|
||||
<a data-bs-toggle="tab" class="nav-link link-anchor" href="#attachments">{% trans %}admin.attachments{% endtrans %}</a>
|
||||
</li>
|
||||
{% if entity.parameters is defined %}
|
||||
{% if entity.parameters is defined and showParameters == true %}
|
||||
<li class="nav-item">
|
||||
<a data-bs-toggle="tab" class="nav-link link-anchor" href="#parameters">{% trans %}admin.parameters{% endtrans %}</a>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
<hr>
|
||||
{{ form_row(form.partname_regex) }}
|
||||
{{ form_row(form.partname_hint) }}
|
||||
{{ form_row(form.part_ipn_prefix) }}
|
||||
<hr>
|
||||
{{ form_row(form.default_description) }}
|
||||
{{ form_row(form.default_comment) }}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
{% extends "admin/base_admin.html.twig" %}
|
||||
|
||||
{% import "vars.macro.twig" as vars %}
|
||||
|
||||
{% block card_title %}
|
||||
<i class="fa-solid fa-coins"></i> {% trans %}currency.caption{% endtrans %}
|
||||
{% endblock %}
|
||||
|
|
@ -20,8 +22,8 @@
|
|||
{{ form_row(form.exchange_rate) }}
|
||||
{% if entity.inverseExchangeRate %}
|
||||
<p class="form-text text-muted offset-3 col-9">
|
||||
{{ '1'|format_currency(default_currency) }} = {{ entity.inverseExchangeRate.tofloat | format_currency(entity.isoCode, {fraction_digit: 5}) }}<br>
|
||||
{{ '1'|format_currency(entity.isoCode) }} = {{ entity.exchangeRate.tofloat | format_currency(default_currency, {fraction_digit: 5}) }}
|
||||
{{ '1'|format_currency(vars.base_currency()) }} = {{ entity.inverseExchangeRate.tofloat | format_currency(entity.isoCode, {fraction_digit: 5}) }}<br>
|
||||
{{ '1'|format_currency(entity.isoCode) }} = {{ entity.exchangeRate.tofloat | format_currency(vars.base_currency(), {fraction_digit: 5}) }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
|||
14
templates/admin/part_custom_state_admin.html.twig
Normal file
14
templates/admin/part_custom_state_admin.html.twig
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{% extends "admin/base_admin.html.twig" %}
|
||||
|
||||
{% block card_title %}
|
||||
<i class="fas fa-balance-scale fa-tools"></i> {% trans %}part_custom_state.caption{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block edit_title %}
|
||||
{% trans %}part_custom_state.edit{% endtrans %}: {{ entity.name }}
|
||||
{% endblock %}
|
||||
|
||||
{% block new_title %}
|
||||
{% trans %}part_custom_state.new{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
{% import "vars.macro.twig" as vars %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ app.request.locale | replace({"_": "-"}) }}"
|
||||
{# For the UX translator, just use the language part (before the _. should be 2 chars), otherwise it finds no translations #}
|
||||
|
|
@ -31,21 +33,19 @@
|
|||
<link rel="mask-icon" href="{{ asset('icon/safari-pinned-tab.svg') }}" color="#5bbad5">
|
||||
|
||||
{# The content block is already escaped. so we must not escape it again. #}
|
||||
<title>{% apply trim|raw %}{% block title %}{{ partdb_title }}{% endblock %}{% endapply %}</title>
|
||||
<title>{% apply trim|raw %}{% block title %}{{ vars.partdb_title() }}{% endblock %}{% endapply %}</title>
|
||||
{% set current_page_title = block("title")|raw %}
|
||||
{% block stylesheets %}
|
||||
{# Include the main bootstrap theme based on user/global setting #}
|
||||
|
||||
|
||||
|
||||
{% if not app.user.theme is defined or app.user.theme is null %}
|
||||
{% set theme = global_theme %}
|
||||
{% if app.user.theme is not defined or app.user.theme is null %}
|
||||
{% set theme = settings_instance('customization').theme %}
|
||||
{% else %}
|
||||
{% set theme = app.user.theme %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
{% if theme and theme in available_themes and encore_entry_exists('theme_' ~ theme) %}
|
||||
{{ encore_entry_link_tags('theme_' ~ theme) }}
|
||||
{% else %}
|
||||
|
|
@ -53,17 +53,19 @@
|
|||
{% endif %}
|
||||
|
||||
{{ encore_entry_link_tags('app') }}
|
||||
|
||||
{% set table_settings = settings_instance('table') %}
|
||||
<style>
|
||||
:root {
|
||||
--table-image-preview-min-size: {{ table_settings.previewImageMinWidth }}px;
|
||||
--table-image-preview-max-size: {{ table_settings.previewImageMaxWidth }}px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ encore_entry_script_tags('app') }}
|
||||
{{ encore_entry_script_tags('webauthn_tfa') }}
|
||||
|
||||
{# load translation files for ckeditor #}
|
||||
{% set two_chars_locale = app.request.locale|default("en")|slice(0,2) %}
|
||||
{% if two_chars_locale != "en" %}
|
||||
<script src="{{ asset("build/ckeditor_translations/" ~ two_chars_locale ~ ".js") }}"></script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body data-base-url="{{ path('homepage', {'_locale': app.request.locale}) }}" data-locale="{{ app.request.locale|default("en")|slice(0,2) }}">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
{% extends "bundles/TwigBundle/Exception/error.html.twig" %}
|
||||
|
||||
{% block status_comment %}
|
||||
Nice try! But you are not allowed to do this!
|
||||
Nice try! But you are not allowed to do this!<br>
|
||||
<code>{{ exception.message }}</code>
|
||||
<br> <small>If you think you should have access to this ressource, contact the adminstrator.</small>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -29,9 +29,7 @@
|
|||
|
||||
<input type="hidden" name="ids" {{ stimulus_target('elements/datatables/parts', 'selectIDs') }} value="">
|
||||
|
||||
<div class="d-none mb-2" {{ stimulus_target('elements/datatables/parts', 'selectPanel') }}>
|
||||
{# <span id="select_count"></span> #}
|
||||
|
||||
<div class="d-none mb-2 bg-body-tertiary shadow-sm border border-secondary rounded mx-2 p-2" {{ stimulus_target('elements/datatables/parts', 'selectPanel') }}>
|
||||
<div class="input-group">
|
||||
<button class="btn btn-outline-secondary" type="button" {{ stimulus_action('elements/datatables/parts', 'invertSelection')}}
|
||||
title="{% trans %}part_list.action.invert_selection{% endtrans %}" ><i class="fa-solid fa-arrow-right-arrow-left"></i></button>
|
||||
|
|
@ -41,7 +39,7 @@
|
|||
|
||||
<select class="form-select" name="action" data-controller="elements--select" {{ stimulus_action('elements/datatables/parts', 'updateTargetPicker', 'change') }}
|
||||
title="{% trans %}part_list.action.action.title{% endtrans %}" required>
|
||||
<optgroup label="{% trans %}part_list.action.action.group.favorite{% endtrans %}">
|
||||
<optgroup label="{% trans %}part_list.action.action.group.favorite{% endtrans %} ({% trans %}part_list.action.scrollable_hint{% endtrans %})">
|
||||
<option {% if not is_granted('@parts.change_favorite') %}disabled{% endif %} value="favorite">{% trans %}part_list.action.action.favorite{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts.change_favorite') %}disabled{% endif %} value="unfavorite">{% trans %}part_list.action.action.unfavorite{% endtrans %}</option>
|
||||
</optgroup>
|
||||
|
|
@ -72,6 +70,10 @@
|
|||
<option {% if not is_granted('@parts.read') %}disabled{% endif %} value="export_csv" data-url="{{ path('select_export_level')}}" data-turbo="false">{% trans %}part_list.action.export_csv{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts.read') %}disabled{% endif %} value="export_yaml" data-url="{{ path('select_export_level')}}" data-turbo="false">{% trans %}part_list.action.export_yaml{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts.read') %}disabled{% endif %} value="export_xml" data-url="{{ path('select_export_level')}}" data-turbo="false">{% trans %}part_list.action.export_xml{% endtrans %}</option>
|
||||
<option {% if not is_granted('@parts.read') %}disabled{% endif %} value="export_xlsx" data-url="{{ path('select_export_level')}}" data-turbo="false">{% trans %}part_list.action.export_xlsx{% endtrans %}</option>
|
||||
</optgroup>
|
||||
<optgroup label="{% trans %}part_list.action.action.info_provider{% endtrans %}">
|
||||
<option {% if not is_granted('@info_providers.create_parts') %}disabled{% endif %} value="bulk_info_provider_import" data-url="{{ path('bulk_info_provider_step1')}}" data-turbo="false">{% trans %}part_list.action.bulk_info_provider_import{% endtrans %}</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
|
||||
|
|
@ -95,4 +97,4 @@
|
|||
</div>
|
||||
</form>
|
||||
|
||||
{% endmacro %}
|
||||
{% endmacro %}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
['footprints', path('tree_footprint_root'), 'footprint.labelp', is_granted('@footprints.read') and is_granted('@parts.read')],
|
||||
['manufacturers', path('tree_manufacturer_root'), 'manufacturer.labelp', is_granted('@manufacturers.read') and is_granted('@parts.read')],
|
||||
['suppliers', path('tree_supplier_root'), 'supplier.labelp', is_granted('@suppliers.read') and is_granted('@parts.read')],
|
||||
['devices', path('tree_device_root'), 'project.labelp', is_granted('@projects.read')],
|
||||
['projects', path('tree_device_root'), 'project.labelp', is_granted('@projects.read')],
|
||||
['tools', path('tree_tools'), 'tools.label', true],
|
||||
] %}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
{% extends 'bootstrap_5_horizontal_layout.html.twig' %}
|
||||
|
||||
{%- block toggle_password_widget -%}
|
||||
<div class="{{ toggle_container_classes|join(' ') }}">{{ block('password_widget') }}</div>
|
||||
{%- endblock toggle_password_widget -%}
|
||||
|
||||
{# Make form rows smaller #}
|
||||
{% block form_row -%}
|
||||
{%- set row_attr = row_attr|merge({"class": "mb-2"}) -%}
|
||||
|
|
@ -139,4 +143,4 @@
|
|||
{% else %}
|
||||
{{- parent() -}}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -70,18 +70,20 @@
|
|||
{% endif %}
|
||||
|
||||
{% if show_presets %}
|
||||
{# This hidden field is there to ensure that none of the presets is submitted, if a user presses enter #}
|
||||
<input type="submit" name="group_admin_form[save]" class="d-none">
|
||||
<div class="col text-end">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{% trans %}permission.preset.button{% endtrans %}
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><button type="submit" name="permission_preset" value="read_only" class="dropdown-item" >{% trans %}permission.preset.read_only{% endtrans%} <br><small class="text-muted">{% trans %}permission.preset.read_only.desc{% endtrans%}</small></button></li>
|
||||
<li><button type="submit" name="permission_preset" value="editor" class="dropdown-item" >{% trans %}permission.preset.editor{% endtrans%} <br><small class="text-muted">{% trans %}permission.preset.editor.desc{% endtrans%}</small></button></li>
|
||||
<li><button type="submit" name="permission_preset" value="admin" class="dropdown-item" >{% trans %}permission.preset.admin{% endtrans%} <br><small class="text-muted">{% trans %}permission.preset.admin.desc{% endtrans%}</small></button></li>
|
||||
<li><button type="submit" name="permission_preset" value="read_only" class="dropdown-item">{% trans %}permission.preset.read_only{% endtrans%} <br><small class="text-muted">{% trans %}permission.preset.read_only.desc{% endtrans%}</small></button></li>
|
||||
<li><button type="submit" name="permission_preset" value="editor" class="dropdown-item">{% trans %}permission.preset.editor{% endtrans%} <br><small class="text-muted">{% trans %}permission.preset.editor.desc{% endtrans%}</small></button></li>
|
||||
<li><button type="submit" name="permission_preset" value="admin" class="dropdown-item">{% trans %}permission.preset.admin{% endtrans%} <br><small class="text-muted">{% trans %}permission.preset.admin.desc{% endtrans%}</small></button></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><button type="submit" name="permission_preset" value="all_inherit" class="dropdown-item" >{% trans %}permission.preset.all_inherit{% endtrans%}<br><small class="text-muted">{% trans %}permission.preset.all_inherit.desc{% endtrans%}</small></button></li>
|
||||
<li><button type="submit" name="permission_preset" value="all_forbid" class="dropdown-item" >{% trans %}permission.preset.all_forbid{% endtrans%}<br><small class="text-muted">{% trans %}permission.preset.all_forbid.desc{% endtrans%}</small></button></li>
|
||||
<li><button type="submit" name="permission_preset" value="all_inherit" class="dropdown-item">{% trans %}permission.preset.all_inherit{% endtrans%}<br><small class="text-muted">{% trans %}permission.preset.all_inherit.desc{% endtrans%}</small></button></li>
|
||||
<li><button type="submit" name="permission_preset" value="all_forbid" class="dropdown-item">{% trans %}permission.preset.all_forbid{% endtrans%}<br><small class="text-muted">{% trans %}permission.preset.all_forbid.desc{% endtrans%}</small></button></li>
|
||||
<li><button type="submit" name="permission_preset" value="all_allow" class="dropdown-item" >{% trans %}permission.preset.all_allow{% endtrans%}<br><small class="text-muted">{% trans %}permission.preset.all_allow.desc{% endtrans%}</small></button></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -110,4 +112,4 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
25
templates/form/settings_form.html.twig
Normal file
25
templates/form/settings_form.html.twig
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{% extends "form/extended_bootstrap_layout.html.twig" %}
|
||||
|
||||
{% block form_label %}
|
||||
{# If parameter_envvar exists on form then show it as tooltip #}
|
||||
{% if parameter_envvar is defined and parameter_envvar is not null %}
|
||||
{%- set label_attr = label_attr|merge({title: 'settings.tooltip.overrideable_by_env'|trans(arguments = {'%env%': (parameter_envvar)|trim})}) -%}
|
||||
{% endif %}
|
||||
{{- parent() -}}
|
||||
{% endblock %}
|
||||
|
||||
{% block checkbox_radio_label %}
|
||||
{# If parameter_envvar exists on form then show it as tooltip #}
|
||||
{% if parameter_envvar is defined and parameter_envvar is not null %}
|
||||
{%- set label_attr = label_attr|merge({title: 'settings.tooltip.overrideable_by_env'|trans(arguments = {'%env%': (parameter_envvar)|trim})}) -%}
|
||||
{% endif %}
|
||||
{{- parent() -}}
|
||||
{% endblock %}
|
||||
|
||||
{% block tristate_label %}
|
||||
{# If parameter_envvar exists on form then show it as tooltip #}
|
||||
{% if parameter_envvar is defined and parameter_envvar is not null %}
|
||||
{%- set label_attr = label_attr|merge({title: 'settings.tooltip.overrideable_by_env'|trans(arguments = {'%env%': (parameter_envvar)|trim})}) -%}
|
||||
{% endif %}
|
||||
{{- parent() -}}
|
||||
{% endblock %}
|
||||
|
|
@ -214,11 +214,11 @@
|
|||
{% endmacro %}
|
||||
|
||||
{% macro parameters_table(parameters) %}
|
||||
<table class="table table-hover table-striped table-sm">
|
||||
<table class="table table-hover table-striped table-sm" style="table-layout: fixed;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}specifications.property{% endtrans %}</th>
|
||||
<th>{% trans %}specifications.symbol{% endtrans %}</th>
|
||||
<th class="col-sm-1">{% trans %}specifications.symbol{% endtrans %}</th>
|
||||
<th>{% trans %}specifications.value{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -240,4 +240,4 @@
|
|||
{% else %}
|
||||
{{ datetime|format_datetime }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% endmacro %}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,25 @@
|
|||
|
||||
{% import "components/new_version.macro.html.twig" as nv %}
|
||||
{% import "components/search.macro.html.twig" as search %}
|
||||
{% import "vars.macro.twig" as vars %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if is_granted('@system.show_updates') %}
|
||||
{{ nv.new_version_alert(new_version_available, new_version, new_version_url) }}
|
||||
{% endif %}
|
||||
|
||||
{% block item_search %}
|
||||
{% if is_granted('@parts.read') %}
|
||||
{{ search.search_form("standalone") }}
|
||||
<div class="mb-2"></div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block item_banner %}
|
||||
<div class="rounded p-4 bg-body-secondary">
|
||||
<h1 class="display-3">{{ partdb_title }}</h1>
|
||||
<h4>
|
||||
{% trans %}version.caption{% endtrans %}: {{ shivas_app_version }}
|
||||
{% if git_branch is not empty or git_commit is not empty %}
|
||||
({{ git_branch ?? '' }}/{{ git_commit ?? '' }})
|
||||
{% endif %}
|
||||
</h4>
|
||||
<h1 class="display-3">{{ vars.partdb_title() }}</h1>
|
||||
{% if settings_instance('customization').showVersionOnHomepage %}
|
||||
<h4>
|
||||
{% trans %}version.caption{% endtrans %}: {{ shivas_app_version }}
|
||||
{% if git_branch is not empty or git_commit is not empty %}
|
||||
({{ git_branch ?? '' }}/{{ git_commit ?? '' }})
|
||||
{% endif %}
|
||||
</h4>
|
||||
{% endif %}
|
||||
{% if banner is not empty %}
|
||||
<hr>
|
||||
<div class="latex" data-controller="common--latex">
|
||||
|
|
@ -30,9 +28,11 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block item_first_steps %}
|
||||
{% if show_first_steps %}
|
||||
<div class="card border-info mt-3">
|
||||
<div class="card border-info">
|
||||
<div class="card-header bg-info ">
|
||||
<h4><i class="fa fa-circle-play fa-fw " aria-hidden="true"></i> {% trans %}homepage.first_steps.title{% endtrans %}</h4>
|
||||
</div>
|
||||
|
|
@ -50,8 +50,10 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="card border-primary mt-3">
|
||||
{% block item_license %}
|
||||
<div class="card border-primary">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h4><i class="fa fa-book fa-fw" aria-hidden="true"></i> {% trans %}homepage.license{% endtrans %}</h4>
|
||||
</div>
|
||||
|
|
@ -67,9 +69,11 @@
|
|||
<strong><i class="fas fa-comments fa-fw"></i> {% trans %}homepage.forum.caption{% endtrans %}:</strong> {% trans with {'%href%': 'https://github.com/Part-DB/Part-DB-server/discussions'}%}homepage.forum.text{% endtrans %}<br>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block item_last_activity %}
|
||||
{% if datatable is not null %}
|
||||
<div class="card mt-3">
|
||||
<div class="card">
|
||||
<div class="card-header"><i class="fas fa-fw fa-history"></i> {% trans %}homepage.last_activity{% endtrans %}</div>
|
||||
<div class="card-body">
|
||||
{% import "components/history_log_macros.html.twig" as log %}
|
||||
|
|
@ -77,4 +81,23 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if is_granted('@system.show_updates') %}
|
||||
{{ nv.new_version_alert(new_version_available, new_version, new_version_url) }}
|
||||
{% endif %}
|
||||
|
||||
{% for item in settings_instance('customization').homepageitems %}
|
||||
{% if block('item_' ~ item.value) is defined %}
|
||||
{{ block('item_' ~ item.value) }}
|
||||
<div class="mb-2"></div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning mt-3" role="alert">
|
||||
Alert: The homepage item "{{ item.value }}" is not defined!
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
124
templates/info_providers/bulk_import/manage.html.twig
Normal file
124
templates/info_providers/bulk_import/manage.html.twig
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
{% extends "main_card.html.twig" %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}info_providers.bulk_import.manage_jobs{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
<i class="fas fa-tasks"></i> {% trans %}info_providers.bulk_import.manage_jobs{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_content %}
|
||||
|
||||
<div data-controller="bulk-job-manage"
|
||||
data-bulk-job-manage-delete-url-value="{{ path('bulk_info_provider_delete', {'jobId': '__JOB_ID__'}) }}"
|
||||
data-bulk-job-manage-stop-url-value="{{ path('bulk_info_provider_stop', {'jobId': '__JOB_ID__'}) }}"
|
||||
data-bulk-job-manage-delete-confirm-message-value="{% trans %}info_providers.bulk_import.confirm_delete_job{% endtrans %}"
|
||||
data-bulk-job-manage-stop-confirm-message-value="{% trans %}info_providers.bulk_import.confirm_stop_job{% endtrans %}">
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<p class="text-muted mb-0">
|
||||
{% trans %}info_providers.bulk_import.manage_jobs_description{% endtrans %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if jobs is not empty %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}info_providers.bulk_import.job_name{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.parts_count{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.results_count{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.progress{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.status{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.created_by{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.created_at{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.completed_at{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.action.label{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for job in jobs %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ job.displayNameKey|trans(job.displayNameParams) }} - {{ job.formattedTimestamp }}</strong>
|
||||
{% if job.isInProgress %}
|
||||
<span class="badge bg-info ms-2">Active</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ job.partCount }}</td>
|
||||
<td>{{ job.resultCount }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="progress me-2" style="width: 80px; height: 12px;">
|
||||
<div class="progress-bar {% if job.isCompleted %}bg-success{% elseif job.isFailed %}bg-danger{% else %}bg-info{% endif %}"
|
||||
role="progressbar"
|
||||
style="width: {{ job.progressPercentage }}%"
|
||||
aria-valuenow="{{ job.progressPercentage }}"
|
||||
aria-valuemin="0" aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted">{{ job.progressPercentage }}%</small>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
{% trans with {'%current%': job.completedPartsCount + job.skippedPartsCount, '%total%': job.partCount} %}info_providers.bulk_import.progress_label{% endtrans %}
|
||||
</small>
|
||||
</td>
|
||||
<td>
|
||||
{% if job.isPending %}
|
||||
<span class="badge bg-warning">{% trans %}info_providers.bulk_import.status.pending{% endtrans %}</span>
|
||||
{% elseif job.isInProgress %}
|
||||
<span class="badge bg-info">{% trans %}info_providers.bulk_import.status.in_progress{% endtrans %}</span>
|
||||
{% elseif job.isCompleted %}
|
||||
<span class="badge bg-success">{% trans %}info_providers.bulk_import.status.completed{% endtrans %}</span>
|
||||
{% elseif job.isStopped %}
|
||||
<span class="badge bg-secondary">{% trans %}info_providers.bulk_import.status.stopped{% endtrans %}</span>
|
||||
{% elseif job.isFailed %}
|
||||
<span class="badge bg-danger">{% trans %}info_providers.bulk_import.status.failed{% endtrans %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ job.createdBy.fullName(true) }}</td>
|
||||
<td>{{ job.createdAt|format_datetime('short') }}</td>
|
||||
<td>
|
||||
{% if job.completedAt %}
|
||||
{{ job.completedAt|format_datetime('short') }}
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
{% if job.isInProgress or job.isCompleted or job.isStopped %}
|
||||
<a href="{{ path('bulk_info_provider_step2', {'jobId': job.id}) }}" class="btn btn-primary">
|
||||
<i class="fas fa-eye"></i> {% trans %}info_providers.bulk_import.view_results{% endtrans %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if job.canBeStopped %}
|
||||
<button type="button" class="btn btn-warning" data-action="click->bulk-job-manage#stopJob" data-job-id="{{ job.id }}">
|
||||
<i class="fas fa-stop"></i> {% trans %}info_providers.bulk_import.action.stop{% endtrans %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if job.isCompleted or job.isFailed or job.isStopped %}
|
||||
<button type="button" class="btn btn-danger" data-action="click->bulk-job-manage#deleteJob" data-job-id="{{ job.id }}">
|
||||
<i class="fas fa-trash"></i> {% trans %}info_providers.bulk_import.action.delete{% endtrans %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
{% trans %}info_providers.bulk_import.no_jobs_found{% endtrans %}<br>
|
||||
{% trans %}info_providers.bulk_import.create_first_job{% endtrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
304
templates/info_providers/bulk_import/step1.html.twig
Normal file
304
templates/info_providers/bulk_import/step1.html.twig
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
{% extends "main_card.html.twig" %}
|
||||
|
||||
{% import "info_providers/providers.macro.html.twig" as providers_macro %}
|
||||
{% import "helper.twig" as helper %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}info_providers.bulk_import.step1.title{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
<i class="fas fa-cloud-arrow-down"></i> {% trans %}info_providers.bulk_import.step1.title{% endtrans %}
|
||||
<span class="badge bg-secondary">{{ parts|length }} {% trans %}info_providers.bulk_import.parts_selected{% endtrans %}</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block card_content %}
|
||||
|
||||
<div>
|
||||
|
||||
<!-- Show existing jobs -->
|
||||
{% if existing_jobs is not empty %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{% trans %}info_providers.bulk_import.existing_jobs{% endtrans %}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}info_providers.bulk_import.job_name{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.parts_count{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.results_count{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.progress{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.status{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.created_at{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.action.label{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for job in existing_jobs %}
|
||||
<tr>
|
||||
<td>{{ job.displayNameKey|trans(job.displayNameParams) }} - {{ job.formattedTimestamp }}</td>
|
||||
<td>{{ job.partCount }}</td>
|
||||
<td>{{ job.resultCount }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="progress me-2" style="width: 60px; height: 8px;">
|
||||
<div class="progress-bar {% if job.isCompleted %}bg-success{% else %}bg-info{% endif %}"
|
||||
role="progressbar"
|
||||
style="width: {{ job.progressPercentage }}%"
|
||||
aria-valuenow="{{ job.progressPercentage }}"
|
||||
aria-valuemin="0" aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted">{{ job.progressPercentage }}%</small>
|
||||
</div>
|
||||
<small class="text-muted">{{ job.completedPartsCount }}/{{ job.partCount }}</small>
|
||||
</td>
|
||||
<td>
|
||||
{% if job.isPending %}
|
||||
<span class="badge bg-warning">{% trans %}info_providers.bulk_import.status.pending{% endtrans %}</span>
|
||||
{% elseif job.isInProgress %}
|
||||
<span class="badge bg-info">{% trans %}info_providers.bulk_import.status.in_progress{% endtrans %}</span>
|
||||
{% elseif job.isCompleted %}
|
||||
<span class="badge bg-success">{% trans %}info_providers.bulk_import.status.completed{% endtrans %}</span>
|
||||
{% elseif job.isFailed %}
|
||||
<span class="badge bg-danger">{% trans %}info_providers.bulk_import.status.failed{% endtrans %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ job.createdAt|date('Y-m-d H:i') }}</td>
|
||||
<td>
|
||||
{% if job.isInProgress or job.isCompleted %}
|
||||
<a href="{{ path('bulk_info_provider_step2', {'jobId': job.id}) }}" class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-eye"></i> {% trans %}info_providers.bulk_import.view_results{% endtrans %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
{% trans %}info_providers.bulk_import.step1.global_mapping_description{% endtrans %}
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success" role="alert">
|
||||
<i class="fas fa-lightbulb"></i>
|
||||
<strong>{% trans %}info_providers.bulk_import.priority_system.title{% endtrans %}:</strong> {% trans %}info_providers.bulk_import.priority_system.description{% endtrans %}
|
||||
<br><small class="text-muted">
|
||||
{% trans %}info_providers.bulk_import.priority_system.example{% endtrans %}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
{% trans %}info_providers.bulk_import.step1.spn_recommendation{% endtrans %}
|
||||
</div>
|
||||
|
||||
<!-- Show selected parts -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{% trans %}info_providers.bulk_import.selected_parts{% endtrans %}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
{% for part in parts %}
|
||||
{% set hasNoIdentifiers = part.manufacturerProductNumber is empty and part.orderdetails is empty %}
|
||||
<div class="col-md-6 col-lg-4 mb-2">
|
||||
<div class="d-flex align-items-center {% if hasNoIdentifiers %}text-danger{% endif %}">
|
||||
<i class="fas fa-microchip {% if hasNoIdentifiers %}text-danger{% else %}text-primary{% endif %} me-2"></i>
|
||||
<div>
|
||||
<a href="{{ path('app_part_show', {'id': part.id}) }}" class="text-decoration-none {% if hasNoIdentifiers %}text-danger{% endif %}">
|
||||
<strong>{{ part.name }}</strong>
|
||||
{% if part.manufacturerProductNumber %}
|
||||
<br><small class="{% if hasNoIdentifiers %}text-danger{% else %}text-muted{% endif %}">MPN: {{ part.manufacturerProductNumber }}</small>
|
||||
{% endif %}
|
||||
{% if part.orderdetails is not empty %}
|
||||
<br><small class="{% if hasNoIdentifiers %}text-danger{% else %}text-muted{% endif %}">
|
||||
SPNs: {{ part.orderdetails|map(od => od.supplierPartNr)|join(', ') }}
|
||||
</small>
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
<div class="card"
|
||||
data-controller="field-mapping"
|
||||
data-field-mapping-mapping-index-value="{{ form.field_mappings|length }}"
|
||||
data-field-mapping-max-mappings-value="{{ fieldChoices|length }}"
|
||||
data-field-mapping-prototype-value="{{ form_widget(form.field_mappings.vars.prototype)|e('html_attr') }}"
|
||||
data-field-mapping-max-mappings-reached-message-value="{{ 'info_providers.bulk_import.max_mappings_reached'|trans|e('js') }}">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{% trans %}info_providers.bulk_import.field_mappings{% endtrans %}</h5>
|
||||
<small class="text-muted">{% trans %}info_providers.bulk_import.field_mappings_help{% endtrans %}</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}info_providers.bulk_search.search_field{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_search.providers{% endtrans %}</th>
|
||||
<th width="80">{% trans %}info_providers.bulk_search.priority{% endtrans %}</th>
|
||||
<th width="100">{% trans %}info_providers.bulk_import.actions.label{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="field-mappings-tbody" data-field-mapping-target="tbody">
|
||||
{% for mapping in form.field_mappings %}
|
||||
<tr class="mapping-row">
|
||||
<td>{{ form_widget(mapping.field) }}{{ form_errors(mapping.field) }}</td>
|
||||
<td>{{ form_widget(mapping.providers) }}{{ form_errors(mapping.providers) }}</td>
|
||||
<td>{{ form_widget(mapping.priority) }}{{ form_errors(mapping.priority) }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-danger btn-sm" data-action="click->field-mapping#removeMapping">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="button" class="btn btn-success btn-sm" id="addMappingBtn"
|
||||
data-field-mapping-target="addButton"
|
||||
data-action="click->field-mapping#addMapping">
|
||||
<i class="fas fa-plus"></i> {% trans %}info_providers.bulk_import.add_mapping{% endtrans %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2 d-flex flex-column align-items-start gap-2">
|
||||
<div class="mb-2">
|
||||
<a href="{{ path('info_providers_list') }}">{% trans %}info_providers.search.info_providers_list{% endtrans %}</a>
|
||||
|
|
||||
<a href="{{ path('bulk_info_provider_manage') }}">{% trans %}info_providers.bulk_import.manage_jobs{% endtrans %}</a>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-2">
|
||||
{{ form_widget(form.prefetch_details, {'attr': {'class': 'form-check-input'}}) }}
|
||||
{{ form_label(form.prefetch_details, null, {'label_attr': {'class': 'form-check-label'}}) }}
|
||||
{{ form_help(form.prefetch_details) }}
|
||||
</div>
|
||||
|
||||
{{ form_widget(form.submit, {'attr': {'class': 'btn btn-primary', 'data-field-mapping-target': 'submitButton'}}) }}
|
||||
</div>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
{% if search_results is not null %}
|
||||
<hr>
|
||||
<h4>{% trans %}info_providers.bulk_import.search_results.title{% endtrans %}</h4>
|
||||
|
||||
{% for part_result in search_results %}
|
||||
{% set part = part_result.part %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
{{ part.name }}
|
||||
{% if part_result.errors is not empty %}
|
||||
<span class="badge bg-warning">{{ part_result.errors|length }} {% trans %}info_providers.bulk_import.errors{% endtrans %}</span>
|
||||
{% endif %}
|
||||
<span class="badge bg-success">{{ part_result.search_results|length }} {% trans %}info_providers.bulk_import.results_found{% endtrans %}</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if part_result.errors is not empty %}
|
||||
{% for error in part_result.errors %}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if part_result.search_results|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{% trans %}name.label{% endtrans %}</th>
|
||||
<th>{% trans %}description.label{% endtrans %}</th>
|
||||
<th>{% trans %}manufacturer.label{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.table.provider.label{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.source_field{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.action.label{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for result in part_result.search_results %}
|
||||
{% set dto = result.dto %}
|
||||
{% set localPart = result.localPart %}
|
||||
<tr {% if localPart is not null %}class="table-warning"{% endif %}>
|
||||
<td>
|
||||
<img src="{{ dto.preview_image_url }}" data-thumbnail="{{ dto.preview_image_url }}"
|
||||
class="hoverpic" style="max-width: 30px;" {{ stimulus_controller('elements/hoverpic') }}>
|
||||
</td>
|
||||
<td>
|
||||
{% if dto.provider_url is not null %}
|
||||
<a href="{{ dto.provider_url }}" target="_blank" rel="noopener">{{ dto.name }}</a>
|
||||
{% else %}
|
||||
{{ dto.name }}
|
||||
{% endif %}
|
||||
{% if dto.mpn is not null %}
|
||||
<br><small class="text-muted">{{ dto.mpn }}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ dto.description }}</td>
|
||||
<td>{{ dto.manufacturer ?? '' }}</td>
|
||||
<td>
|
||||
{{ info_provider_label(dto.provider_key)|default(dto.provider_key) }}
|
||||
<br><small class="text-muted">{{ dto.provider_id }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info">{{ result.source_field ?? 'unknown' }}</span>
|
||||
{% if result.source_keyword %}
|
||||
<br><small class="text-muted">{{ result.source_keyword }}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group-vertical btn-group-sm" role="group">
|
||||
{% set updateHref = path('info_providers_update_part',
|
||||
{'id': part.id, 'providerKey': dto.provider_key, 'providerId': dto.provider_id}) %}
|
||||
<a class="btn btn-primary" href="{{ updateHref }}" target="_blank">
|
||||
<i class="fas fa-edit"></i> {% trans %}info_providers.bulk_import.update_part{% endtrans %}
|
||||
</a>
|
||||
|
||||
{% if localPart is not null %}
|
||||
<a class="btn btn-info btn-sm" href="{{ path('app_part_show', {'id': localPart.id}) }}" target="_blank">
|
||||
<i class="fas fa-eye"></i> {% trans %}info_providers.bulk_import.view_existing{% endtrans %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
{% trans %}info_providers.search.no_results{% endtrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
240
templates/info_providers/bulk_import/step2.html.twig
Normal file
240
templates/info_providers/bulk_import/step2.html.twig
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
{% extends "main_card.html.twig" %}
|
||||
|
||||
{% import "info_providers/providers.macro.html.twig" as providers_macro %}
|
||||
{% import "helper.twig" as helper %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}info_providers.bulk_import.step2.title{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
<i class="fas fa-search"></i> {% trans %}info_providers.bulk_import.step2.title{% endtrans %}
|
||||
<span class="badge bg-secondary">{{ job.displayNameKey|trans(job.displayNameParams) }} - {{ job.formattedTimestamp }}</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block card_content %}
|
||||
|
||||
<div {{ stimulus_controller('bulk-import', {
|
||||
'jobId': job.id,
|
||||
'researchUrl': path('bulk_info_provider_research_part', {'jobId': job.id, 'partId': '__PART_ID__'}),
|
||||
'researchAllUrl': path('bulk_info_provider_research_all', {'jobId': job.id}),
|
||||
'markCompletedUrl': path('bulk_info_provider_mark_completed', {'jobId': job.id, 'partId': '__PART_ID__'}),
|
||||
'markSkippedUrl': path('bulk_info_provider_mark_skipped', {'jobId': job.id, 'partId': '__PART_ID__'}),
|
||||
'markPendingUrl': path('bulk_info_provider_mark_pending', {'jobId': job.id, 'partId': '__PART_ID__'})
|
||||
}) }}>
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h5 class="mb-1">{{ job.displayNameKey|trans(job.displayNameParams) }} - {{ job.formattedTimestamp }}</h5>
|
||||
<small class="text-muted">
|
||||
{{ job.partCount }} {% trans %}info_providers.bulk_import.parts{% endtrans %} •
|
||||
{{ job.resultCount }} {% trans %}info_providers.bulk_import.results{% endtrans %} •
|
||||
{% trans %}info_providers.bulk_import.created_at{% endtrans %}: {{ job.createdAt|date('Y-m-d H:i') }}
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
{% if job.isPending %}
|
||||
<span class="badge bg-warning">{% trans %}info_providers.bulk_import.status.pending{% endtrans %}</span>
|
||||
{% elseif job.isInProgress %}
|
||||
<span class="badge bg-info">{% trans %}info_providers.bulk_import.status.in_progress{% endtrans %}</span>
|
||||
{% elseif job.isCompleted %}
|
||||
<span class="badge bg-success">{% trans %}info_providers.bulk_import.status.completed{% endtrans %}</span>
|
||||
{% elseif job.isFailed %}
|
||||
<span class="badge bg-danger">{% trans %}info_providers.bulk_import.status.failed{% endtrans %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h6 class="mb-0">Progress</h6>
|
||||
<span data-bulk-import-target="progressText">{{ job.completedPartsCount }} / {{ job.partCount }} completed</span>
|
||||
</div>
|
||||
<div class="progress" style="height: 8px;">
|
||||
<div data-bulk-import-target="progressBar" class="progress-bar" role="progressbar"
|
||||
style="width: {{ job.progressPercentage }}%"
|
||||
aria-valuenow="{{ job.progressPercentage }}" aria-valuemin="0" aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-2">
|
||||
<small class="text-muted">
|
||||
<span id="completed-count">{{ job.completedPartsCount }}</span> {% trans %}info_providers.bulk_import.completed{% endtrans %} •
|
||||
<span id="skipped-count">{{ job.skippedPartsCount }}</span> {% trans %}info_providers.bulk_import.skipped{% endtrans %}
|
||||
</small>
|
||||
<small class="text-muted"><span id="progress-percentage">{{ job.progressPercentage }}%</span></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tutorial/Instructions -->
|
||||
<div class="alert alert-info mb-4" role="alert">
|
||||
<h6 class="alert-heading">
|
||||
<i class="fas fa-info-circle"></i> {% trans %}info_providers.bulk_import.step2.instructions.title{% endtrans %}
|
||||
</h6>
|
||||
<p class="mb-2">{% trans %}info_providers.bulk_import.step2.instructions.description{% endtrans %}</p>
|
||||
<ul class="mb-0 ps-3">
|
||||
<li>{% trans %}info_providers.bulk_import.step2.instructions.step1{% endtrans %}</li>
|
||||
<li>{% trans %}info_providers.bulk_import.step2.instructions.step2{% endtrans %}</li>
|
||||
<li>{% trans %}info_providers.bulk_import.step2.instructions.step3{% endtrans %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Research Controls -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="mb-1">{% trans %}info_providers.bulk_import.research.title{% endtrans %}</h6>
|
||||
<small class="text-muted">{% trans %}info_providers.bulk_import.research.description{% endtrans %}</small>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm me-2"
|
||||
data-action="click->bulk-import#researchAllParts"
|
||||
id="research-all-btn">
|
||||
<span class="spinner-border spinner-border-sm me-1" style="display: none;" id="research-all-spinner"></span>
|
||||
<i class="fas fa-search"></i> {% trans %}info_providers.bulk_import.research.all_pending{% endtrans %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for part_result in search_results %}
|
||||
{# @var part_result \App\Services\InfoProviderSystem\DTOs\BulkSearchPartResultsDTO #}
|
||||
|
||||
{% set part = part_result.part %}
|
||||
{% set isCompleted = job.isPartCompleted(part.id) %}
|
||||
{% set isSkipped = job.isPartSkipped(part.id) %}
|
||||
<div class="card mb-3 {% if isCompleted %}border-success{% elseif isSkipped %}border-warning{% endif %}"
|
||||
data-part-id="{{ part.id }}"
|
||||
{% if isCompleted %}style="background-color: rgba(25, 135, 84, 0.1);"{% endif %}>
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h5 class="card-title mb-0">
|
||||
<a href="{{ path('app_part_show', {'id': part.id}) }}" class="text-decoration-none">
|
||||
{{ part.name }}
|
||||
</a>
|
||||
{% if isCompleted %}
|
||||
<span class="badge bg-success">
|
||||
<i class="fas fa-check"></i> {% trans %}info_providers.bulk_import.completed{% endtrans %}
|
||||
</span>
|
||||
{% elseif isSkipped %}
|
||||
<span class="badge bg-warning">
|
||||
<i class="fas fa-forward"></i> {% trans %}info_providers.bulk_import.skipped{% endtrans %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if part_result.errors is not empty %}
|
||||
<span class="badge bg-danger">{% trans with {'%count%': part_result.errors|length} %}info_providers.bulk_import.errors{% endtrans %}</span>
|
||||
{% endif %}
|
||||
<span class="badge bg-info">{% trans with {'%count%': part_result.searchResults|length} %}info_providers.bulk_import.results_found{% endtrans %}</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-info btn-sm"
|
||||
data-action="click->bulk-import#researchPart"
|
||||
data-part-id="{{ part.id }}"
|
||||
title="{% trans %}info_providers.bulk_import.research.part_tooltip{% endtrans %}">
|
||||
<span class="spinner-border spinner-border-sm me-1" style="display: none;" data-research-spinner="{{ part.id }}"></span>
|
||||
<i class="fas fa-search"></i> {% trans %}info_providers.bulk_import.research.part{% endtrans %}
|
||||
</button>
|
||||
{% if not isCompleted and not isSkipped %}
|
||||
<button type="button" class="btn btn-success btn-sm" data-action="click->bulk-import#markCompleted" data-part-id="{{ part.id }}">
|
||||
<i class="fas fa-check"></i> {% trans %}info_providers.bulk_import.mark_completed{% endtrans %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning btn-sm" data-action="click->bulk-import#markSkipped" data-part-id="{{ part.id }}">
|
||||
<i class="fas fa-forward"></i> {% trans %}info_providers.bulk_import.mark_skipped{% endtrans %}
|
||||
</button>
|
||||
{% elseif isCompleted %}
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-action="click->bulk-import#markPending" data-part-id="{{ part.id }}">
|
||||
<i class="fas fa-undo"></i> {% trans %}info_providers.bulk_import.mark_pending{% endtrans %}
|
||||
</button>
|
||||
{% elseif isSkipped %}
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-action="click->bulk-import#markPending" data-part-id="{{ part.id }}">
|
||||
<i class="fas fa-undo"></i> {% trans %}info_providers.bulk_import.mark_pending{% endtrans %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if part_result.errors is not empty %}
|
||||
{% for error in part_result.errors %}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if part_result.searchResults|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{% trans %}name.label{% endtrans %}</th>
|
||||
<th>{% trans %}description.label{% endtrans %}</th>
|
||||
<th>{% trans %}manufacturer.label{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.table.provider.label{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.source_field{% endtrans %}</th>
|
||||
<th>{% trans %}info_providers.bulk_import.action.label{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for result in part_result.searchResults %}
|
||||
{# @var result \App\Services\InfoProviderSystem\DTOs\BulkSearchPartResultDTO #}
|
||||
{% set dto = result.searchResult %}
|
||||
{% set localPart = result.localPart %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ dto.preview_image_url }}" data-thumbnail="{{ dto.preview_image_url }}"
|
||||
class="hoverpic" style="max-width: 35px;" {{ stimulus_controller('elements/hoverpic') }}>
|
||||
</td>
|
||||
<td>
|
||||
{% if dto.provider_url is not null %}
|
||||
<a href="{{ dto.provider_url }}" target="_blank" rel="noopener">{{ dto.name }}</a>
|
||||
{% else %}
|
||||
{{ dto.name }}
|
||||
{% endif %}
|
||||
{% if dto.mpn is not null %}
|
||||
<br><small class="text-muted">{{ dto.mpn }}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ dto.description }}</td>
|
||||
<td>{{ dto.manufacturer ?? '' }}</td>
|
||||
<td>
|
||||
{{ info_provider_label(dto.provider_key)|default(dto.provider_key) }}
|
||||
<br><small class="text-muted">{{ dto.provider_id }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info">{{ result.sourceField ?? 'unknown' }}</span>
|
||||
{% if result.sourceKeyword %}
|
||||
<br><small class="text-muted">{{ result.sourceKeyword }}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group-vertical btn-group-sm" role="group">
|
||||
{% set updateHref = path('info_providers_update_part',
|
||||
{'id': part.id, 'providerKey': dto.provider_key, 'providerId': dto.provider_id}) ~ '?jobId=' ~ job.id %}
|
||||
<a class="btn btn-primary{% if isCompleted %} disabled{% endif %}" href="{% if not isCompleted %}{{ updateHref }}{% else %}#{% endif %}"{% if isCompleted %} aria-disabled="true"{% endif %}>
|
||||
<i class="fas fa-edit"></i> {% trans %}info_providers.bulk_import.update_part{% endtrans %}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
{% trans %}info_providers.search.no_results{% endtrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -13,7 +13,6 @@
|
|||
{% else %}
|
||||
{{ provider.providerInfo.name | trans }}
|
||||
{% endif %}
|
||||
|
||||
</h5>
|
||||
<div>
|
||||
{% if provider.providerInfo.description is defined and provider.providerInfo.description is not null %}
|
||||
|
|
@ -23,6 +22,11 @@
|
|||
|
||||
</div>
|
||||
<div class="col-6">
|
||||
{% if provider.providerInfo.settings_class is defined %}
|
||||
<a href="{{ path('info_providers_provider_settings', {'provider': provider.providerKey}) }}" class="btn btn-primary btn-sm {% if not is_granted('@config.change_system_settings') %}disabled{% endif %}"
|
||||
title="{% trans %}info_providers.settings.title{% endtrans %}"
|
||||
><i class="fa-solid fa-cog"></i></a>
|
||||
{% endif %}
|
||||
{% for capability in provider.capabilities %}
|
||||
{# @var capability \App\Services\InfoProviderSystem\Providers\ProviderCapabilities #}
|
||||
<span class="badge text-bg-secondary">
|
||||
|
|
@ -52,4 +56,4 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
{% endmacro %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
{% extends "main_card.html.twig" %}
|
||||
{% macro genId(widget) %}{{ widget.vars.full_name }}{% endmacro %}
|
||||
|
||||
{% form_theme form "form/settings_form.html.twig" %}
|
||||
|
||||
{% block title %}{% trans %}info_providers.settings.title{% endtrans %}: {{ info_provider_info.name }}{% endblock %}
|
||||
|
||||
{% block card_title %}<i class="fa-solid fa-gear fa-fw"></i> {% trans %}info_providers.settings.title{% endtrans %}: <b>{{ info_provider_info.name }}</b>{% endblock %}
|
||||
|
||||
{% block card_content %}
|
||||
<div class="offset-sm-3">
|
||||
<h3>
|
||||
{% if info_provider_info.url %}
|
||||
<a href="{{ info_provider_info.url }}" class="link-external" target="_blank" rel="nofollow">{{ info_provider_info.name }}</a>
|
||||
{% else %}
|
||||
{{ info_provider_info.name }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
{% if info_provider_info.description %}
|
||||
<p class="text-muted">{{ info_provider_info.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{{ form_start(form) }}
|
||||
<div class="row">
|
||||
<div class="offset-sm-3 col mb-3 ps-2">
|
||||
<b>{{ form_help(form) }}</b>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
|
|
@ -10,6 +10,9 @@
|
|||
{% block card_content %}
|
||||
{{ form_start(form) }}
|
||||
|
||||
{# Default submit to use when pressing enter. #}
|
||||
<input type="submit" name="label_dialog[update]" class="d-none">
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-bs-toggle="tab" id="common-tab" role="tab" aria-controls="common" aria-selected="true" href="#common"
|
||||
|
|
@ -112,6 +115,10 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if form.update_profile is defined %}
|
||||
{{ form_row(form.update_profile) }}
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="offset-sm-3 col-sm-9">
|
||||
<div class="input-group">
|
||||
|
|
@ -145,4 +152,4 @@
|
|||
</object>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
{% import "vars.macro.twig" as vars %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ meta_title }}</title>
|
||||
<meta name="author" content="{{ partdb_title }}">
|
||||
<meta name="author" content="{{ vars.partdb_title() }}">
|
||||
<meta name="description" content="Label for {{ meta_title }}">
|
||||
<meta name="keywords" content="Part-DB, Label, Barcode">
|
||||
<style>
|
||||
|
|
@ -14,7 +16,10 @@
|
|||
</head>
|
||||
<body>
|
||||
{% set start_page %}
|
||||
<div class="page" style="break-after: always;"><table col="{{ options.xcount }}" row="{{ options.ycount }}"><tr>
|
||||
{# The page div ensures the page breaks, while the page-inner elements restrict the content to the page size. Sine dompdf 3.1.1 we cannot apply the position: absolute; to the page element directly. #}
|
||||
<div class="page">
|
||||
<div class="page-inner">
|
||||
<table col="{{ options.xcount }}" row="{{ options.ycount }}"><tr>
|
||||
{% endset %}
|
||||
|
||||
{% set end_page %}
|
||||
|
|
@ -60,4 +65,4 @@
|
|||
{% endfor %}
|
||||
{{ end_page }}
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -3,17 +3,31 @@
|
|||
}
|
||||
|
||||
.page {
|
||||
/** We cannot apply the position: absolute trick here, because then dompdf will not respect the page break anymore **/
|
||||
|
||||
page-break-inside: avoid;
|
||||
page-break-before: avoid;
|
||||
page-break-after: always;
|
||||
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.page-inner {
|
||||
/* Absolute position prevents automatic page breaks */
|
||||
/*position: absolute;*/
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
page-break-inside: avoid;
|
||||
page-break-before: avoid;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
/* Last page should not break */
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
{% import "vars.macro.twig" as vars %}
|
||||
|
||||
{% apply inky_to_html|inline_css(source('@css/email/foundation-emails.css'), source('@css/email/email.css')) %}
|
||||
|
||||
<container>
|
||||
|
|
@ -5,7 +7,7 @@
|
|||
<row class="header">
|
||||
<columns>
|
||||
<spacer size="16"></spacer>
|
||||
<h4 class="text-center"><a href="{{ url('homepage') }}">{{ partdb_title }}</a></h4>
|
||||
<h4 class="text-center"><a href="{{ url('homepage') }}">{{ vars.partdb_title() }}</a></h4>
|
||||
</columns>
|
||||
</row>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,16 @@
|
|||
{{ form_row(form.needsReview) }}
|
||||
{{ form_row(form.favorite) }}
|
||||
{{ form_row(form.mass) }}
|
||||
{{ form_row(form.ipn) }}
|
||||
{{ form_row(form.partUnit) }}
|
||||
<div {{ stimulus_controller('elements/ipn_suggestion', {
|
||||
partId: part.id,
|
||||
partCategoryId: part.category ? part.category.id : null,
|
||||
partDescription: part.description,
|
||||
suggestions: ipnSuggestions,
|
||||
'commonSectionHeader': 'part.edit.tab.advanced.ipn.commonSectionHeader'|trans,
|
||||
'partIncrementHeader': 'part.edit.tab.advanced.ipn.partIncrementHeader'|trans,
|
||||
'suggestUrl': url('ipn_suggestions')
|
||||
}) }}>
|
||||
{{ form_row(form.ipn) }}
|
||||
</div>
|
||||
{{ form_row(form.partUnit) }}
|
||||
{{ form_row(form.partCustomState) }}
|
||||
|
|
@ -4,6 +4,32 @@
|
|||
{% trans with {'%name%': part.name|escape } %}part.edit.title{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block before_card %}
|
||||
{% if bulk_job and jobId %}
|
||||
<div class="alert alert-info mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="{{ path('bulk_info_provider_step2', {jobId: bulk_job.id}) }}" class="btn btn-outline-primary btn-sm me-2">
|
||||
<i class="fas fa-arrow-left fa-fw" aria-hidden="true"></i>
|
||||
{% trans %}info_providers.bulk_import.back{% endtrans %}
|
||||
</a>
|
||||
<form method="post" action="{{ path('part_bulk_import_complete', {id: part.id, jobId: bulk_job.id}) }}" style="display: inline;">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token('bulk_complete_' ~ part.id) }}">
|
||||
<button type="submit" class="btn btn-primary btn-sm me-3">
|
||||
<i class="fas fa-check fa-fw" aria-hidden="true"></i>
|
||||
{% trans %}info_providers.bulk_import.complete{% endtrans %}
|
||||
</button>
|
||||
</form>
|
||||
<div>
|
||||
<i class="fas fa-cloud-download fa-fw" aria-hidden="true"></i>
|
||||
{% trans %}info_providers.bulk_import.editing_part{% endtrans %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
<i class="fas fa-edit fa-fw" aria-hidden="true"></i>
|
||||
{% trans with {'%name%': part.name|escape } %}part.edit.card_title{% endtrans %}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,19 @@
|
|||
{% block card_border %}border-info{% endblock %}
|
||||
{% block card_type %}bg-info text-bg-info{% endblock %}
|
||||
|
||||
{% block before_card %}
|
||||
{% if bulk_job and jobId %}
|
||||
<div class="alert alert-info mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<i class="fas fa-cloud-download fa-fw" aria-hidden="true"></i>
|
||||
{% trans %}info_providers.bulk_import.editing_part{% endtrans %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}info_providers.update_part.title{% endtrans %}: {{ merge_old_name }}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
<div class="carousel-item {% if loop.first %}active{% endif %}">
|
||||
<a href="{{ entity_url(pic, 'file_view') }}" data-turbo="false" target="_blank" rel="noopener">
|
||||
<img class="d-block w-100 img-fluid img-thumbnail bg-light part-info-image" src="{{ entity_url(pic, 'file_view') }}" alt="">
|
||||
{% if img_overlay %}
|
||||
{% if settings_instance("part_info").showPartImageOverlay %}
|
||||
<div class="mask"></div>
|
||||
<div class="carousel-caption-hover">
|
||||
<div class="carousel-caption text-white">
|
||||
|
|
|
|||
|
|
@ -36,6 +36,19 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if part.partCustomState is not null %}
|
||||
<div class="mt-1">
|
||||
<h6>
|
||||
<span class="badge bg-primary" title="{% trans %}part_custom_state.caption{% endtrans %}"><i class="fas fa-tools fa-fw"></i> {{ part.partCustomState.name }}</span>
|
||||
|
||||
{% if part.partCustomState is not null and part.partCustomState.masterPictureAttachment and attachment_manager.fileExisting(part.partCustomState.masterPictureAttachment) %}
|
||||
<br/>
|
||||
<img class="img-fluid img-thumbnail thumbnail-sm" src="{{ attachment_thumbnail(part.partCustomState.masterPictureAttachment, 'thumbnail_md') }}" alt="{% trans %}attachment.preview.alt{% endtrans %}" />
|
||||
{% endif %}
|
||||
</h6>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Favorite Status tag #}
|
||||
{% if part.favorite %}
|
||||
<div class="mt-1">
|
||||
|
|
@ -79,4 +92,4 @@
|
|||
</a>
|
||||
</h6>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
{% for name, parameters in part.groupedParameters %}
|
||||
{% if name is not empty %}<h5 class="mt-1">{{ name }}</h5>{% endif %}
|
||||
{{ helper.parameters_table(parameters) }}
|
||||
{% if not loop.last %}
|
||||
<hr class="my-0">
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if description_params is not empty %}
|
||||
|
|
@ -14,4 +17,4 @@
|
|||
{% if comment_params is not empty %}
|
||||
<h5 class="mt-1">{% trans %}parameters.auto_extracted_from_comment{% endtrans %}</h5>
|
||||
{{ helper.parameters_table(comment_params) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@
|
|||
<button class="nav-link" id="filter-projects-tab" data-bs-toggle="tab" data-bs-target="#filter-projects"><i class="fas fa-archive fa-fw"></i> {% trans %}project.labelp{% endtrans %}</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if filterForm.inBulkImportJob is defined %}
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="filter-bulk-import-tab" data-bs-toggle="tab" data-bs-target="#filter-bulk-import"><i class="fas fa-download fa-fw"></i> {% trans %}part.edit.tab.bulk_import{% endtrans %}</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
{{ form_start(filterForm, {"attr": {"data-controller": "helpers--form-cleanup", "data-action": "helpers--form-cleanup#submit"}}) }}
|
||||
|
|
@ -56,6 +61,7 @@
|
|||
{{ form_row(filterForm.favorite) }}
|
||||
{{ form_row(filterForm.needsReview) }}
|
||||
{{ form_row(filterForm.measurementUnit) }}
|
||||
{{ form_row(filterForm.partCustomState) }}
|
||||
{{ form_row(filterForm.mass) }}
|
||||
{{ form_row(filterForm.dbId) }}
|
||||
{{ form_row(filterForm.ipn) }}
|
||||
|
|
@ -126,6 +132,13 @@
|
|||
{{ form_row(filterForm.bomComment) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if filterForm.inBulkImportJob is defined %}
|
||||
<div class="tab-pane pt-3" id="filter-bulk-import" role="tabpanel" aria-labelledby="filter-bulk-import-tab" tabindex="0">
|
||||
{{ form_row(filterForm.inBulkImportJob) }}
|
||||
{{ form_row(filterForm.bulkImportJobStatus) }}
|
||||
{{ form_row(filterForm.bulkImportPartStatus) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
186
templates/projects/_bom_validation_results.html.twig
Normal file
186
templates/projects/_bom_validation_results.html.twig
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
{# BOM Validation Results Component #}
|
||||
{#
|
||||
Usage:
|
||||
{% include 'projects/_bom_validation_results.html.twig' with {
|
||||
validation_result: validation_result,
|
||||
show_summary: true,
|
||||
show_details: true
|
||||
} %}
|
||||
#}
|
||||
|
||||
{% if validation_result is defined and validation_result is not empty %}
|
||||
{% set stats = validation_result %}
|
||||
|
||||
{# Validation Summary #}
|
||||
{% if show_summary is defined and show_summary %}
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fa-solid fa-chart-bar fa-fw"></i>
|
||||
{% trans %}project.bom_import.validation.summary{% endtrans %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<div class="h3 text-primary">{{ stats.total_entries }}</div>
|
||||
<small class="text-muted">{% trans %}project.bom_import.validation.total_entries{% endtrans %}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<div class="h3 text-success">{{ stats.valid_entries }}</div>
|
||||
<small class="text-muted">{% trans %}project.bom_import.validation.valid_entries{% endtrans %}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<div class="h3 text-warning">{{ stats.invalid_entries }}</div>
|
||||
<small class="text-muted">{% trans %}project.bom_import.validation.invalid_entries{% endtrans %}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<div class="h3 text-info">
|
||||
{% if stats.total_entries > 0 %}
|
||||
{{ ((stats.valid_entries / stats.total_entries) * 100) | round(1) }}%
|
||||
{% else %}
|
||||
0%
|
||||
{% endif %}
|
||||
</div>
|
||||
<small class="text-muted">{% trans %}project.bom_import.validation.success_rate{% endtrans %}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Validation Messages #}
|
||||
{% if validation_result.errors is defined and validation_result.errors is not empty %}
|
||||
<div class="alert alert-danger">
|
||||
<h4><i class="fa-solid fa-exclamation-triangle fa-fw"></i> {% trans %}project.bom_import.validation.errors.title{% endtrans %}</h4>
|
||||
<p class="mb-2">{% trans %}project.bom_import.validation.errors.description{% endtrans %}</p>
|
||||
<ul class="mb-0">
|
||||
{% for error in validation_result.errors %}
|
||||
<li>{{ error|raw }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if validation_result.warnings is defined and validation_result.warnings is not empty %}
|
||||
<div class="alert alert-warning">
|
||||
<h4><i class="fa-solid fa-exclamation-circle fa-fw"></i> {% trans %}project.bom_import.validation.warnings.title{% endtrans %}</h4>
|
||||
<p class="mb-2">{% trans %}project.bom_import.validation.warnings.description{% endtrans %}</p>
|
||||
<ul class="mb-0">
|
||||
{% for warning in validation_result.warnings %}
|
||||
<li>{{ warning|raw }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if validation_result.info is defined and validation_result.info is not empty %}
|
||||
<div class="alert alert-info">
|
||||
<h4><i class="fa-solid fa-info-circle fa-fw"></i> {% trans %}project.bom_import.validation.info.title{% endtrans %}</h4>
|
||||
<ul class="mb-0">
|
||||
{% for info in validation_result.info %}
|
||||
<li>{{ info|raw }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Detailed Line-by-Line Results #}
|
||||
{% if show_details is defined and show_details and validation_result.line_results is defined %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fa-solid fa-list fa-fw"></i>
|
||||
{% trans %}project.bom_import.validation.details.title{% endtrans %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}project.bom_import.validation.details.line{% endtrans %}</th>
|
||||
<th>{% trans %}project.bom_import.validation.details.status{% endtrans %}</th>
|
||||
<th>{% trans %}project.bom_import.validation.details.messages{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for line_result in validation_result.line_results %}
|
||||
<tr class="{% if line_result.is_valid %}table-success{% else %}table-danger{% endif %}">
|
||||
<td>
|
||||
<strong>{{ line_result.line_number }}</strong>
|
||||
</td>
|
||||
<td>
|
||||
{% if line_result.is_valid %}
|
||||
<span class="badge bg-success">
|
||||
<i class="fa-solid fa-check fa-fw"></i>
|
||||
{% trans %}project.bom_import.validation.details.valid{% endtrans %}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger">
|
||||
<i class="fa-solid fa-times fa-fw"></i>
|
||||
{% trans %}project.bom_import.validation.details.invalid{% endtrans %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if line_result.errors is not empty %}
|
||||
<div class="text-danger">
|
||||
{% for error in line_result.errors %}
|
||||
<div><i class="fa-solid fa-exclamation-triangle fa-fw"></i> {{ error|raw }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if line_result.warnings is not empty %}
|
||||
<div class="text-warning">
|
||||
{% for warning in line_result.warnings %}
|
||||
<div><i class="fa-solid fa-exclamation-circle fa-fw"></i> {{ warning|raw }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if line_result.info is not empty %}
|
||||
<div class="text-info">
|
||||
{% for info in line_result.info %}
|
||||
<div><i class="fa-solid fa-info-circle fa-fw"></i> {{ info|raw }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Action Buttons #}
|
||||
{% if validation_result.is_valid is defined %}
|
||||
<div class="mt-3">
|
||||
{% if validation_result.is_valid %}
|
||||
<div class="alert alert-success">
|
||||
<i class="fa-solid fa-check-circle fa-fw"></i>
|
||||
{% trans %}project.bom_import.validation.all_valid{% endtrans %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-danger">
|
||||
<i class="fa-solid fa-exclamation-triangle fa-fw"></i>
|
||||
{% trans %}project.bom_import.validation.fix_errors{% endtrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
@ -8,7 +8,8 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block card_content %}
|
||||
{% set can_build = buildHelper.projectBuildable(project, number_of_builds) %}
|
||||
{% set bom_empty = project.bomEntries | length == 0 %}
|
||||
{% set can_build = not bom_empty and buildHelper.projectBuildable(project, number_of_builds) %}
|
||||
{% import "components/projects.macro.html.twig" as project_macros %}
|
||||
|
||||
{% if project.status is not empty and project.status != "in_production" %}
|
||||
|
|
@ -17,8 +18,10 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="alert {% if can_build %}alert-success{% else %}alert-danger{% endif %}" role="alert">
|
||||
{% if not can_build %}
|
||||
<div class="alert {% if can_build %}alert-success{% elseif bom_empty%}alert-warning{% else %}alert-danger{% endif %}" role="alert">
|
||||
{% if bom_empty %}
|
||||
<h5><i class="fa-solid fa-circle-exclamation fa-fw"></i> {% trans %}project.builds.no_bom_entries{% endtrans %}</h5>
|
||||
{% elseif not can_build %}
|
||||
<h5><i class="fa-solid fa-circle-exclamation fa-fw"></i> {% trans %}project.builds.build_not_possible{% endtrans %}</h5>
|
||||
<b>{% trans with {"%number_of_builds%": number_of_builds} %}project.builds.following_bom_entries_miss_instock_n{% endtrans %}</b>
|
||||
<ul>
|
||||
|
|
@ -37,4 +40,4 @@
|
|||
{% include 'projects/build/_form.html.twig' %}
|
||||
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
204
templates/projects/import_bom_map_fields.html.twig
Normal file
204
templates/projects/import_bom_map_fields.html.twig
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
{% extends "main_card.html.twig" %}
|
||||
|
||||
{% block title %}{% trans %}project.bom_import.map_fields{% endtrans %}{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
<i class="fa-solid fa-arrows-left-right fa-fw"></i>
|
||||
{% trans %}project.bom_import.map_fields{% endtrans %}{% if project %}: <i>{{ project.name }}</i>{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_content %}
|
||||
{% if validation_result is defined %}
|
||||
{% include 'projects/_bom_validation_results.html.twig' with {
|
||||
validation_result: validation_result,
|
||||
show_summary: true,
|
||||
show_details: false
|
||||
} %}
|
||||
{% endif %}
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<i class="fa-solid fa-info-circle fa-fw"></i>
|
||||
{% trans %}project.bom_import.map_fields.help{% endtrans %}
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
<i class="fa-solid fa-lightbulb fa-fw"></i>
|
||||
{% trans %}project.bom_import.field_mapping.priority_note{% endtrans %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form_start(form) }}
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
{{ form_row(form.delimiter) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fa-solid fa-table-columns fa-fw"></i>
|
||||
{% trans %}project.bom_import.field_mapping.title{% endtrans %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}project.bom_import.field_mapping.csv_field{% endtrans %}</th>
|
||||
<th>{% trans %}project.bom_import.field_mapping.maps_to{% endtrans %}</th>
|
||||
<th>{% trans %}project.bom_import.field_mapping.suggestion{% endtrans %}</th>
|
||||
<th>{% trans %}project.bom_import.field_mapping.priority{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for field in detected_fields %}
|
||||
<tr>
|
||||
<td>
|
||||
<code>{{ field }}</code>
|
||||
</td>
|
||||
<td>
|
||||
{{ form_widget(form['mapping_' ~ field_name_mapping[field]], {
|
||||
'attr': {
|
||||
'class': 'form-select field-mapping-select',
|
||||
'data-field': field
|
||||
}
|
||||
}) }}
|
||||
</td>
|
||||
<td>
|
||||
{% if suggested_mapping[field] is defined %}
|
||||
<span class="badge bg-success">
|
||||
<i class="fa-solid fa-magic fa-fw"></i>
|
||||
{{ suggested_mapping[field] }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-muted">
|
||||
<i class="fa-solid fa-question fa-fw"></i>
|
||||
{% trans %}project.bom_import.field_mapping.no_suggestion{% endtrans %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<input type="number"
|
||||
class="form-control form-control-sm priority-input"
|
||||
min="1"
|
||||
value="10"
|
||||
style="width: 80px;"
|
||||
data-field="{{ field }}"
|
||||
title="{% trans %}project.bom_import.field_mapping.priority_help{% endtrans %}">
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h6>{% trans %}project.bom_import.field_mapping.summary{% endtrans %}:</h6>
|
||||
<div id="mapping-summary" class="alert alert-info">
|
||||
<i class="fa-solid fa-info-circle fa-fw"></i>
|
||||
{% trans %}project.bom_import.field_mapping.select_to_see_summary{% endtrans %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
{{ form_widget(form.submit, {
|
||||
'attr': {
|
||||
'class': 'btn btn-primary'
|
||||
}
|
||||
}) }}
|
||||
<a href="{{ path('project_import_bom', {'id': project.id}) }}" class="btn btn-secondary">
|
||||
<i class="fa-solid fa-arrow-left fa-fw"></i>
|
||||
{% trans %}common.back{% endtrans %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{ form_end(form) }}
|
||||
|
||||
<script nonce="{{ csp_nonce('script') }}">
|
||||
// Function to initialize the field mapping page
|
||||
function initializeFieldMapping() {
|
||||
const suggestions = {{ suggested_mapping|json_encode|raw }};
|
||||
const fieldNameMapping = {{ field_name_mapping|json_encode|raw }};
|
||||
|
||||
Object.keys(suggestions).forEach(function(field) {
|
||||
// Use the sanitized field name from the server-side mapping
|
||||
const sanitizedField = fieldNameMapping[field];
|
||||
const select = document.querySelector('[name="form[mapping_' + sanitizedField + ']"]');
|
||||
if (select && !select.value) {
|
||||
select.value = suggestions[field];
|
||||
}
|
||||
});
|
||||
|
||||
// Update mapping summary
|
||||
updateMappingSummary();
|
||||
|
||||
// Add event listeners for dynamic updates
|
||||
document.querySelectorAll('.field-mapping-select').forEach(function(select) {
|
||||
select.addEventListener('change', updateMappingSummary);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.priority-input').forEach(function(input) {
|
||||
input.addEventListener('change', updateMappingSummary);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize on both DOMContentLoaded and Turbo events
|
||||
document.addEventListener('DOMContentLoaded', initializeFieldMapping);
|
||||
document.addEventListener('turbo:load', initializeFieldMapping);
|
||||
document.addEventListener('turbo:frame-load', function(event) {
|
||||
// Only initialize if this frame contains our field mapping content
|
||||
if (event.target.id === 'content' || event.target.closest('#content')) {
|
||||
initializeFieldMapping();
|
||||
}
|
||||
});
|
||||
|
||||
function updateMappingSummary() {
|
||||
const summary = document.getElementById('mapping-summary');
|
||||
const mappings = {};
|
||||
const priorities = {};
|
||||
|
||||
// Collect all mappings and priorities
|
||||
document.querySelectorAll('.field-mapping-select').forEach(function(select) {
|
||||
const field = select.getAttribute('data-field');
|
||||
const target = select.value;
|
||||
const priorityInput = document.querySelector('.priority-input[data-field="' + field + '"]');
|
||||
const priority = priorityInput ? parseInt(priorityInput.value) || 10 : 10;
|
||||
|
||||
if (target && target !== '') {
|
||||
if (!mappings[target]) {
|
||||
mappings[target] = [];
|
||||
}
|
||||
mappings[target].push({
|
||||
field: field,
|
||||
priority: priority
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Sort by priority and build summary
|
||||
let summaryHtml = '<div class="row">';
|
||||
Object.keys(mappings).forEach(function(target) {
|
||||
const fieldMappings = mappings[target].sort((a, b) => a.priority - b.priority);
|
||||
const fieldList = fieldMappings.map(m => m.field + ' (' + '{{ "project.bom_import.field_mapping.priority_short"|trans }}' + m.priority + ')').join(', ');
|
||||
|
||||
summaryHtml += '<div class="col-md-6 mb-2">';
|
||||
summaryHtml += '<strong>' + target + ':</strong> ' + fieldList;
|
||||
summaryHtml += '</div>';
|
||||
});
|
||||
summaryHtml += '</div>';
|
||||
|
||||
if (Object.keys(mappings).length === 0) {
|
||||
summary.innerHTML = '<i class="fa-solid fa-info-circle fa-fw"></i> {{ "project.bom_import.field_mapping.select_to_see_summary"|trans }}';
|
||||
} else {
|
||||
summary.innerHTML = summaryHtml;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{% set can_build = buildHelper.projectBuildable(project) %}
|
||||
{% set bom_empty = project.bomEntries | length == 0 %}
|
||||
{% set can_build = not bom_empty and buildHelper.projectBuildable(project) %}
|
||||
|
||||
{% import "components/projects.macro.html.twig" as project_macros %}
|
||||
|
||||
|
|
@ -8,8 +9,10 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="alert mt-2 {% if can_build %}alert-success{% else %}alert-danger{% endif %}" role="alert">
|
||||
{% if not can_build %}
|
||||
<div class="alert mt-2 {% if can_build %}alert-success{% elseif bom_empty%}alert-warning{% else %}alert-danger{% endif %}" role="alert">
|
||||
{% if bom_empty %}
|
||||
<h5><i class="fa-solid fa-circle-exclamation fa-fw"></i> {% trans %}project.builds.no_bom_entries{% endtrans %}</h5>
|
||||
{% elseif not can_build %}
|
||||
<h5><i class="fa-solid fa-circle-exclamation fa-fw"></i> {% trans %}project.builds.build_not_possible{% endtrans %}</h5>
|
||||
<b>{% trans %}project.builds.following_bom_entries_miss_instock{% endtrans %}</b>
|
||||
<ul>
|
||||
|
|
@ -19,7 +22,7 @@
|
|||
</ul>
|
||||
{% else %}
|
||||
<h5><i class="fa-solid fa-circle-check fa-fw"></i> {% trans %}project.builds.build_possible{% endtrans %}</h5>
|
||||
<span>{% trans with {"%max_builds%": buildHelper.maximumBuildableCount(project)} %}project.builds.number_of_builds_possible{% endtrans %}</span>
|
||||
<span>{% trans with {"%max_builds%": buildHelper.maximumBuildableCountAsString(project)} %}project.builds.number_of_builds_possible{% endtrans %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
|
@ -27,7 +30,7 @@
|
|||
<div class="row mt-2">
|
||||
<div class="col-4">
|
||||
<div class="input-group mb-3">
|
||||
<input type="number" min="1" class="form-control" placeholder="{% trans %}project.builds.number_of_builds{% endtrans %}" name="n" required>
|
||||
<input type="number" min="1" class="form-control" placeholder="{% trans %}project.builds.number_of_builds{% endtrans %}" name="n" required value="1">
|
||||
<input type="hidden" name="_redirect" value="{{ uri_without_host(app.request) }}">
|
||||
<button class="btn btn-outline-secondary" type="submit" id="button-addon2">{% trans %}project.build.btn_build{% endtrans %}</button>
|
||||
</div>
|
||||
|
|
@ -37,4 +40,4 @@
|
|||
|
||||
{% if project.buildPart %}
|
||||
<p><b>{% trans %}project.builds.no_stocked_builds{% endtrans %}:</b> <a href="{{ entity_url(project.buildPart) }}">{{ project.buildPart.amountSum }}</a></p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@
|
|||
{% block card_content %}
|
||||
<form action="{{ path('login') }}" method="post" data-turbo="false" class="form-horizontal">
|
||||
|
||||
<input type="hidden" name="_csrf_token"
|
||||
value="{{ csrf_token('authenticate') }}">
|
||||
<input type="hidden" name="_csrf_token" data-controller="csrf-protection" value="{{ csrf_token('authenticate') }}">
|
||||
|
||||
<input type="hidden" name="_target_path" value="{{ app.request.query.get('_target_path') }}" />
|
||||
|
||||
|
|
@ -72,4 +71,4 @@
|
|||
{% if allow_email_pw_reset %}
|
||||
<a class="offset-sm-2" href="{{ path('pw_reset_request') }}">{% trans %}pw_reset.password_forget{% endtrans %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
66
templates/settings/settings.html.twig
Normal file
66
templates/settings/settings.html.twig
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
{% extends "main_card.html.twig" %}
|
||||
{% macro genId(widget) %}{{ widget.vars.full_name }}{% endmacro %}
|
||||
|
||||
{% form_theme form "form/settings_form.html.twig" %}
|
||||
|
||||
{% block title %}{% trans %}settings.title{% endtrans %}{% endblock %}
|
||||
|
||||
{% block card_title %}<i class="fa-solid fa-gears fa-fw"></i> {% trans %}settings.title{% endtrans %}{% endblock %}
|
||||
|
||||
{% block card_content %}
|
||||
{{ form_start(form) }}
|
||||
|
||||
{# Tabs #}
|
||||
<ul class="nav nav-tabs">
|
||||
{% for tab_widget in form %}
|
||||
{# Create a tab for each compound form #}
|
||||
{% if tab_widget.vars.compound ?? false %}
|
||||
<li class="nav-item">
|
||||
<button type="button" class="nav-link {% if loop.first %}active{% endif %}" aria-current="page" data-bs-toggle="tab"
|
||||
id="settings-{{ _self.genId(tab_widget) }}-tab" data-bs-target="#settings-{{ _self.genId(tab_widget) }}-pane"
|
||||
>{{ (tab_widget.vars.label ?? tab_widget.vars.name|humanize)|trans }}</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{# Panes #}
|
||||
<div class="tab-content">
|
||||
{% for tab_widget in form %}
|
||||
{# Create a tab for each compound form #}
|
||||
{% if tab_widget.vars.compound ?? false %}
|
||||
<div class="tab-pane fade pt-2 {% if loop.first %}show active{% endif %}" id="settings-{{ _self.genId(tab_widget) }}-pane">
|
||||
{{ form_help(tab_widget) }}
|
||||
{{ form_errors(tab_widget) }}
|
||||
|
||||
{% for section_widget in tab_widget %}
|
||||
{% set settings_object = section_widget.vars.value %}
|
||||
|
||||
{% if section_widget.vars.compound ?? false %}
|
||||
<fieldset>
|
||||
<legend class="offset-3">
|
||||
<i class="fa-solid {{ settings_icon(settings_object)|default('fa-sliders') }} fa-fw"></i>
|
||||
{{ (section_widget.vars.label ?? section_widget.vars.name|humanize)|trans }}
|
||||
</legend>
|
||||
<div class="row">
|
||||
<div class="offset-sm-3 col mb-3 ps-2">
|
||||
<b>{{ form_help(section_widget) }}</b>
|
||||
{{ form_errors(section_widget) }}
|
||||
</div>
|
||||
</div>
|
||||
{{ form_widget(section_widget) }}
|
||||
</fieldset>
|
||||
{% if not loop.last %}
|
||||
<hr class="mx-0 mb-2 mt-2">
|
||||
{% endif %}
|
||||
{% else %} {# If not a compound render as normal row #}
|
||||
{{ form_row(section_widget) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{% import "helper.twig" as helper %}
|
||||
{% import "vars.macro.twig" as vars %}
|
||||
|
||||
<table class="table table-sm table-striped table-hover table-bordered">
|
||||
<tbody>
|
||||
|
|
@ -15,7 +16,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>Part-DB Instance name</td>
|
||||
<td>{{ partdb_title }}</td>
|
||||
<td>{{ vars.partdb_title() }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Default locale</td>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
{% extends "base.html.twig" %}
|
||||
{% import "vars.macro.twig" as vars %}
|
||||
|
||||
{% block title %}{{ partdb_title }} {% trans %}tfa_backup.codes.title{% endtrans %}{% endblock %}
|
||||
{% block title %}{{ vars.partdb_title() }} {% trans %}tfa_backup.codes.title{% endtrans %}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{{ partdb_title }} {% trans %}tfa_backup.codes.title{% endtrans %}
|
||||
{{ vars.partdb_title() }} {% trans %}tfa_backup.codes.title{% endtrans %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{% trans %}tfa_backup.codes.explanation{% endtrans %}</h5>
|
||||
|
|
|
|||
3
templates/vars.macro.twig
Normal file
3
templates/vars.macro.twig
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{% macro partdb_title() %}{{ settings_instance("customization").instanceName }}{% endmacro %}
|
||||
|
||||
{% macro base_currency() %}{{ settings_instance("localization").baseCurrency }}{% endmacro %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue