Merge branch 'master' into ux-selectpanel

This commit is contained in:
d-buchmann 2025-09-09 08:22:46 +02:00 committed by GitHub
commit a43b64bd5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
339 changed files with 18550 additions and 6970 deletions

View file

@ -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">

View file

@ -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>

View file

@ -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 %}

View file

@ -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,6 +53,14 @@
{% 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 %}

View file

@ -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 %}

View file

@ -29,7 +29,7 @@
<input type="hidden" name="ids" {{ stimulus_target('elements/datatables/parts', 'selectIDs') }} value="">
<div class="mb-2" {{ stimulus_target('elements/datatables/parts', 'selectPanel') }}>
<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') }}>
{# <span id="select_count"></span> #}
<div class="input-group">
@ -95,4 +95,4 @@
</div>
</form>
{% endmacro %}
{% endmacro %}

View file

@ -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],
] %}

View file

@ -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 %}

View 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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -100,6 +100,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">
@ -133,4 +137,4 @@
</object>
</div>
{% endif %}
{% endblock %}
{% endblock %}

View file

@ -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>

View file

@ -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>

View file

@ -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">

View 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 %}

View 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 %}

View file

@ -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 %}

View 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 %}

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,3 @@
{% macro partdb_title() %}{{ settings_instance("customization").instanceName }}{% endmacro %}
{% macro base_currency() %}{{ settings_instance("localization").baseCurrency }}{% endmacro %}