Part-DB-server/templates/info_providers/bulk_import/step2.html.twig
Sebastian Almberg 319a455bf2 Add Quick Apply and Apply All buttons to bulk info provider import
Adds the ability to apply provider search results to parts directly
from the bulk import step 2 page without navigating to individual
part edit forms. Includes per-result Quick Apply buttons and an
Apply All button for batch operations.
2026-03-11 12:54:41 +01:00

259 lines
16 KiB
Twig

{% 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__'}),
'quickApplyUrl': path('bulk_info_provider_quick_apply', {'jobId': job.id, 'partId': '__PART_ID__'}),
'quickApplyAllUrl': path('bulk_info_provider_quick_apply_all', {'jobId': job.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>
<button type="button" class="btn btn-success btn-sm"
data-action="click->bulk-import#quickApplyAll"
id="quick-apply-all-btn"
title="{% trans %}info_providers.bulk_import.quick_apply_all.tooltip{% endtrans %}">
<span class="spinner-border spinner-border-sm me-1" style="display: none;" id="quick-apply-all-spinner"></span>
<i class="fas fa-bolt"></i> {% trans %}info_providers.bulk_import.quick_apply_all{% 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">
{% if not isCompleted %}
<button type="button" class="btn btn-success"
data-action="click->bulk-import#quickApply"
data-part-id="{{ part.id }}"
data-provider-key="{{ dto.provider_key }}"
data-provider-id="{{ dto.provider_id }}"
title="{% trans %}info_providers.bulk_import.quick_apply.tooltip{% endtrans %}">
<i class="fas fa-bolt"></i> {% trans %}info_providers.bulk_import.quick_apply{% endtrans %}
</button>
{% endif %}
{% 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 %}