mirror of
https://github.com/System-End/theseus.git
synced 2026-04-19 23:32:49 +00:00
295 lines
No EOL
9.6 KiB
Text
295 lines
No EOL
9.6 KiB
Text
<%# Shared template for mapping CSV fields to address fields %>
|
|
<%
|
|
default_mapping = {
|
|
# loops defaults
|
|
'email' => 'email',
|
|
'firstname' => 'first_name',
|
|
'lastname' => 'last_name',
|
|
'addressline1' => 'line_1',
|
|
'addressline2' => 'line_2',
|
|
'addresscity' => 'city',
|
|
'addressstate' => 'state',
|
|
'addresszipcode' => 'postal_code',
|
|
'addresszip' => 'postal_code',
|
|
'addresscountry' => 'country',
|
|
'rubber_stamps' => 'rubber_stamps',
|
|
# hcb promotions
|
|
'address (zip/postal code)' => 'postal_code',
|
|
'address (city)' => 'city',
|
|
'address (state/province)' => 'state',
|
|
'address (country)' => 'country',
|
|
'address (line 1)' => 'line_1',
|
|
'address (line 2)' => 'line_2',
|
|
'recipient name' => 'first_name',
|
|
'login email' => 'email',
|
|
# unified YSWS DB
|
|
'address (line 1)' => 'line_1',
|
|
'address (line 2)' => 'line_2',
|
|
'city' => 'city',
|
|
'state / province' => 'state',
|
|
'zip / postal code' => 'postal_code',
|
|
'country' => 'country',
|
|
'first name' => 'first_name',
|
|
'last name' => 'last_name',
|
|
'email' => 'email',
|
|
'rubber stamps' => 'rubber_stamps',
|
|
'zip/postal code' => 'postal_code',
|
|
'city' => 'city',
|
|
'state/province' => 'state',
|
|
'country' => 'country',
|
|
'line 1' => 'line_1',
|
|
'line 2' => 'line_2',
|
|
'address' => 'line_1'
|
|
}
|
|
%>
|
|
<div class="container">
|
|
<article>
|
|
<header>
|
|
<h1>Map CSV Fields</h1>
|
|
<p>Select the corresponding address field for each CSV column.</p>
|
|
<p><small>Orange borders indicate automatically mapped fields. You can modify these if needed.</small></p>
|
|
</header>
|
|
<%# List unmapped fields %>
|
|
<%
|
|
mapped_fields = @csv_headers.map { |header| default_mapping[header&.downcase] }.compact
|
|
unmapped_fields = (@address_fields - ["batch_id"]) - mapped_fields
|
|
|
|
# Find required fields that couldn't be automapped
|
|
required_unmapped = BaseBatchesController::REQUIRED_FIELDS.select do |field|
|
|
# Check if any CSV header could potentially map to this field
|
|
!@csv_headers.any? do |header|
|
|
default_mapping[header&.downcase] == field
|
|
end
|
|
end
|
|
|
|
optional_unmapped = unmapped_fields - BaseBatchesController::REQUIRED_FIELDS
|
|
|
|
# Debug output
|
|
Rails.logger.debug "Available fields: #{@address_fields.inspect}"
|
|
%>
|
|
<% if unmapped_fields.any? %>
|
|
<div class="unmapped-fields">
|
|
<h3>Unmapped Fields</h3>
|
|
<% if required_unmapped.any? %>
|
|
<p><strong>Required fields that need mapping:</strong></p>
|
|
<ul>
|
|
<% required_unmapped.each do |field| %>
|
|
<li><%= field.humanize %></li>
|
|
<% end %>
|
|
</ul>
|
|
<% end %>
|
|
<% if optional_unmapped.any? %>
|
|
<p><strong>Optional fields:</strong></p>
|
|
<ul>
|
|
<% optional_unmapped.each do |field| %>
|
|
<li><%= field.humanize %></li>
|
|
<% end %>
|
|
</ul>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
<%= form_with(model: @batch, url: send("set_mapping_#{@batch.class.name.split('::').first.downcase}_batch_path", @batch), method: :post) do |f| %>
|
|
<div class="mapping-container">
|
|
<%
|
|
# Sort headers so automapped fields appear first
|
|
sorted_headers = @csv_headers.sort_by do |header|
|
|
default_mapping[header].present? ? 0 : 1
|
|
end
|
|
%>
|
|
<% sorted_headers.each do |header| %>
|
|
<div class="mapping-row">
|
|
<div class="csv-field">
|
|
<div class="field-name"><%= header %></div>
|
|
<div class="field-preview">
|
|
<% if @csv_preview.first %>
|
|
<%= @csv_preview.first[@csv_headers.index(header)] %>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<div class="field-mapping">
|
|
<%
|
|
default_value = default_mapping[header.downcase]
|
|
is_automapped = default_value.present?
|
|
%>
|
|
<%= select_tag "mapping[#{header}]",
|
|
options_for_select(@address_fields - ["batch_id"], default_value),
|
|
prompt: "Select address field...",
|
|
class: "form-select",
|
|
data: {
|
|
csv_field: header,
|
|
automap_value: default_value
|
|
} %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<%# Add hidden fields for required fields if they're not already mapped %>
|
|
<%
|
|
mapped_fields = @csv_headers.map { |header| default_mapping[header] }.compact
|
|
missing_required = BaseBatchesController::REQUIRED_FIELDS - mapped_fields
|
|
|
|
if missing_required.any?
|
|
# Find a suitable CSV header for each missing required field
|
|
missing_required.each do |field|
|
|
# Try to find a suitable header based on field name
|
|
suitable_header = case field
|
|
when 'first_name'
|
|
@csv_headers.find { |h| h.downcase.include?('name') || h.downcase.include?('recipient') }
|
|
when 'state'
|
|
@csv_headers.find { |h| h.downcase.include?('state') || h.downcase.include?('province') }
|
|
when 'line_1'
|
|
@csv_headers.find { |h| h.downcase.include?('address') || h.downcase.include?('street') }
|
|
when 'city'
|
|
@csv_headers.find { |h| h.downcase.include?('city') || h.downcase.include?('town') }
|
|
when 'postal_code'
|
|
@csv_headers.find { |h| h.downcase.include?('zip') || h.downcase.include?('postal') || h.downcase.include?('code') }
|
|
when 'country'
|
|
@csv_headers.find { |h| h.downcase.include?('country') || h.downcase.include?('nation') }
|
|
end
|
|
|
|
if suitable_header
|
|
# Add a hidden field to map this header to the required field
|
|
hidden_field_tag "mapping[#{suitable_header}]", field
|
|
end
|
|
end
|
|
end
|
|
%>
|
|
<div class="actions">
|
|
<%= f.submit "Save Mapping", class: "contrast" %>
|
|
<%= link_to "Cancel", send("#{@batch.class.name.split('::').first.downcase}_batch_path", @batch), class: "secondary" %>
|
|
</div>
|
|
<% end %>
|
|
</article>
|
|
</div>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Get all select elements
|
|
const selects = document.querySelectorAll('.form-select');
|
|
|
|
// Function to update the disabled state of options and borders
|
|
function updateSelects() {
|
|
// Get all currently selected values (excluding empty selections)
|
|
const selectedValues = Array.from(selects)
|
|
.map(select => select.value)
|
|
.filter(value => value !== '');
|
|
|
|
// For each select element
|
|
selects.forEach(select => {
|
|
const currentValue = select.value;
|
|
|
|
// Reset border color
|
|
select.style.border = '2px solid var(--muted-border-color)';
|
|
|
|
// If this field has an automap value and user hasn't changed it
|
|
if (select.dataset.automapValue && currentValue === select.dataset.automapValue) {
|
|
select.style.border = '2px solid #ffa500'; // Orange for automapped
|
|
}
|
|
|
|
// If user has changed the value from the automapped one
|
|
if (currentValue !== '' && select.dataset.automapValue && currentValue !== select.dataset.automapValue) {
|
|
select.dataset.usermapped = 'true';
|
|
select.style.border = '2px solid #2ecc71'; // Green for user changed
|
|
} else {
|
|
delete select.dataset.usermapped;
|
|
}
|
|
|
|
// First, enable all options
|
|
for (let i = 0; i < select.options.length; i++) {
|
|
select.options[i].disabled = false;
|
|
}
|
|
|
|
// Then disable options that are selected in other dropdowns
|
|
selectedValues.forEach(value => {
|
|
if (value !== currentValue) {
|
|
// Find the option with this value in the current select
|
|
for (let i = 0; i < select.options.length; i++) {
|
|
if (select.options[i].value === value) {
|
|
select.options[i].disabled = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Run the update function immediately
|
|
updateSelects();
|
|
|
|
// Add change event listeners to all selects
|
|
selects.forEach(select => {
|
|
select.addEventListener('change', updateSelects);
|
|
});
|
|
});
|
|
</script>
|
|
<style>
|
|
.mapping-container {
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.mapping-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 0.5rem;
|
|
margin-bottom: 0.5rem;
|
|
padding: 0.5rem;
|
|
background: var(--card-background-color);
|
|
border-radius: var(--border-radius);
|
|
}
|
|
|
|
.field-name {
|
|
font-weight: bold;
|
|
color: var(--primary);
|
|
}
|
|
|
|
.field-preview {
|
|
color: var(--muted-color);
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.form-select {
|
|
width: 100%;
|
|
border: 2px solid var(--muted-border-color);
|
|
border-radius: var(--border-radius);
|
|
transition: border-color 0.2s ease;
|
|
}
|
|
|
|
.form-select option:disabled {
|
|
color: var(--muted-color);
|
|
}
|
|
|
|
.actions {
|
|
margin-top: 1rem;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.unmapped-fields {
|
|
margin: 1rem 0;
|
|
padding: 1rem;
|
|
background: var(--card-background-color);
|
|
border: 1px solid var(--muted-border-color);
|
|
border-radius: var(--border-radius);
|
|
}
|
|
|
|
.unmapped-fields h3 {
|
|
margin-top: 0;
|
|
color: var(--primary);
|
|
}
|
|
|
|
.unmapped-fields ul {
|
|
margin: 0.5rem 0;
|
|
padding-left: 1.5rem;
|
|
}
|
|
|
|
.unmapped-fields li {
|
|
margin: 0.25rem 0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.mapping-row {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style> |