April 2026 waren Wirtschaft Feedback
This commit is contained in:
parent
02f2a4c23e
commit
9ce711d6b2
167 changed files with 25278 additions and 8518 deletions
|
|
@ -42,3 +42,414 @@
|
|||
@include('admin.product.upload_whitelabel')
|
||||
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
@php
|
||||
$ingredient_catalog_for_js = $ingredient_catalog->keyBy('id')->map(function ($item) {
|
||||
return [
|
||||
'id' => $item->id,
|
||||
'name' => $item->name,
|
||||
'inci' => $item->inci,
|
||||
'effect' => $item->effect,
|
||||
'default_factor' => $item->default_factor,
|
||||
'quality_name' => $item->materialQuality?->name ?? '',
|
||||
];
|
||||
});
|
||||
$packaging_catalog_for_js = $packaging_catalog->keyBy('id')->map(function ($item) {
|
||||
return [
|
||||
'name' => $item->name,
|
||||
'weight_grams' => $item->weight_grams,
|
||||
'material_name' => $item->packagingMaterial?->name ?? '',
|
||||
];
|
||||
});
|
||||
@endphp
|
||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
|
||||
<script>
|
||||
(function ($) {
|
||||
var catalog = @json($ingredient_catalog_for_js);
|
||||
var packagingCatalog = @json($packaging_catalog_for_js);
|
||||
|
||||
function parseDeNumber(val) {
|
||||
if (val === undefined || val === null || val === '') return NaN;
|
||||
var s = String(val).replace(/\s/g, '').replace(',', '.');
|
||||
var n = parseFloat(s);
|
||||
return isNaN(n) ? NaN : n;
|
||||
}
|
||||
|
||||
function updateEffective($row) {
|
||||
var g = parseDeNumber($row.find('.pi-gram').val());
|
||||
var f = parseDeNumber($row.find('.pi-factor').val());
|
||||
var $out = $row.find('.pi-effective');
|
||||
if (!isNaN(g) && !isNaN(f)) {
|
||||
var eff = g * f;
|
||||
$out.text(eff.toLocaleString('de-DE', { minimumFractionDigits: 3, maximumFractionDigits: 3 }));
|
||||
} else {
|
||||
$out.text('—');
|
||||
}
|
||||
}
|
||||
|
||||
function updateRecipeTotal() {
|
||||
var total = 0;
|
||||
var hasValue = false;
|
||||
$('#ingredient-sortable-rows tr').each(function () {
|
||||
var v = parseDeNumber($(this).find('.pi-gram').val());
|
||||
if (!isNaN(v)) { total += v; hasValue = true; }
|
||||
});
|
||||
var $cell = $('#recipe-total-percent');
|
||||
if (!hasValue) {
|
||||
$cell.text('—').removeClass('text-danger text-success');
|
||||
return;
|
||||
}
|
||||
var formatted = total.toLocaleString('de-DE', { minimumFractionDigits: 3, maximumFractionDigits: 3 }) + ' %';
|
||||
$cell.text(formatted);
|
||||
var diff = Math.abs(total - 100);
|
||||
if (diff < 0.001) {
|
||||
$cell.removeClass('text-danger').addClass('text-success');
|
||||
$cell.attr('title', '');
|
||||
} else {
|
||||
$cell.removeClass('text-success').addClass('text-danger');
|
||||
$cell.attr('title', 'Die Gesamtrezeptur ergibt nicht 100 %!');
|
||||
}
|
||||
}
|
||||
|
||||
function refreshAllEffectives() {
|
||||
$('#ingredient-sortable-rows tr').each(function () { updateEffective($(this)); });
|
||||
updateRecipeTotal();
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
var $tbody = document.getElementById('ingredient-sortable-rows');
|
||||
if ($tbody && typeof Sortable !== 'undefined') {
|
||||
new Sortable($tbody, {
|
||||
handle: '.ingredient-drag-handle',
|
||||
animation: 150,
|
||||
});
|
||||
}
|
||||
|
||||
function appendIngredientRow(id) {
|
||||
id = String(id);
|
||||
var ing = catalog[id];
|
||||
if (!ing) return;
|
||||
var factor = ing.default_factor != null ? String(ing.default_factor).replace('.', ',') : '1,10';
|
||||
var row = $('<tr data-ingredient-id="' + id + '">' +
|
||||
'<td class="text-muted align-middle ingredient-drag-handle" style="cursor:grab">☰</td>' +
|
||||
'<td class="align-middle"></td><td class="align-middle small text-muted"></td><td class="align-middle"></td>' +
|
||||
'<td><input type="hidden" name="pi_ingredient_id[]" value="' + id + '">' +
|
||||
'<input type="text" name="pi_gram[]" class="form-control form-control-sm pi-gram" value="" autocomplete="off" step="0.001"></td>' +
|
||||
'<td><input type="text" name="pi_factor[]" class="form-control form-control-sm pi-factor" value="' + factor + '" autocomplete="off"></td>' +
|
||||
'<td class="align-middle pi-effective text-right small text-muted">—</td>' +
|
||||
'<td class="align-middle"><a class="text-danger ingredient-row-remove" href="#" title="Entfernen"><i class="far fa-trash-alt"></i></a></td></tr>');
|
||||
row.find('td').eq(1).text(ing.name || '');
|
||||
row.find('td').eq(2).text(ing.quality_name || '—');
|
||||
row.find('td').eq(3).text(ing.inci || '');
|
||||
$('#ingredient-sortable-rows').append(row);
|
||||
updateEffective(row);
|
||||
updateRecipeTotal();
|
||||
}
|
||||
|
||||
function syncIngredientModalRows() {
|
||||
var usedIds = {};
|
||||
$('#ingredient-sortable-rows tr').each(function () {
|
||||
var id = $(this).data('ingredient-id');
|
||||
if (id) usedIds[String(id)] = true;
|
||||
});
|
||||
$('#modal-ingredients-pick .ingredient-modal-row').each(function () {
|
||||
var id = String($(this).data('ingredient-id'));
|
||||
var inUse = !!usedIds[id];
|
||||
var $cb = $(this).find('.js-ingredient-modal-cb');
|
||||
$cb.prop('disabled', inUse);
|
||||
$cb.attr('title', inUse ? '{{ __('Bereits im Produkt') }}' : '');
|
||||
if (inUse) {
|
||||
$cb.prop('checked', false);
|
||||
}
|
||||
$(this).toggleClass('text-muted', inUse);
|
||||
$(this).css('opacity', inUse ? 0.65 : 1);
|
||||
$(this).find('.js-ing-modal-name').toggleClass('text-muted', inUse);
|
||||
$(this).find('.js-ing-modal-inci').toggleClass('text-muted', inUse);
|
||||
});
|
||||
}
|
||||
|
||||
function filterIngredientModalRows() {
|
||||
var q = $('#ingredient-modal-search').val().trim().toLowerCase();
|
||||
var visible = 0;
|
||||
$('#ingredient-modal-tbody .ingredient-modal-row').each(function () {
|
||||
var hay = $(this).attr('data-ingredient-search') || '';
|
||||
var match = !q || hay.indexOf(q) !== -1;
|
||||
$(this).toggle(match);
|
||||
if (match) {
|
||||
visible++;
|
||||
}
|
||||
});
|
||||
$('#ingredient-modal-no-results').toggleClass('d-none', !(q.length > 0 && visible === 0));
|
||||
}
|
||||
|
||||
$('#ingredient-modal-search').on('input', filterIngredientModalRows);
|
||||
|
||||
$('#modal-ingredients-pick').on('show.bs.modal', function () {
|
||||
$('#ingredient-modal-search').val('');
|
||||
$('#ingredient-modal-tbody .ingredient-modal-row').show();
|
||||
$('#ingredient-modal-no-results').addClass('d-none');
|
||||
syncIngredientModalRows();
|
||||
});
|
||||
|
||||
$('#btn-ingredients-modal-add').on('click', function () {
|
||||
var added = 0;
|
||||
$('#modal-ingredients-pick .js-ingredient-modal-cb:checked:not(:disabled)').each(function () {
|
||||
var id = String($(this).val());
|
||||
appendIngredientRow(id);
|
||||
$(this).prop('checked', false);
|
||||
added++;
|
||||
});
|
||||
if (added > 0) {
|
||||
syncIngredientModalRows();
|
||||
$('#modal-ingredients-pick').modal('hide');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.ingredient-row-remove', function (ev) {
|
||||
ev.preventDefault();
|
||||
var $tr = $(this).closest('tr');
|
||||
$tr.remove();
|
||||
syncIngredientModalRows();
|
||||
updateRecipeTotal();
|
||||
});
|
||||
|
||||
$(document).on('input change', '.pi-gram, .pi-factor', function () {
|
||||
updateEffective($(this).closest('tr'));
|
||||
updateRecipeTotal();
|
||||
});
|
||||
|
||||
refreshAllEffectives();
|
||||
|
||||
var $packTbody = document.getElementById('packaging-sortable-rows');
|
||||
if ($packTbody && typeof Sortable !== 'undefined') {
|
||||
new Sortable($packTbody, {
|
||||
handle: '.packaging-drag-handle',
|
||||
animation: 150,
|
||||
});
|
||||
}
|
||||
|
||||
function appendPackagingRow(id) {
|
||||
id = String(id);
|
||||
var pk = packagingCatalog[id];
|
||||
if (!pk) {
|
||||
return;
|
||||
}
|
||||
var wCell = '—';
|
||||
if (pk.weight_grams != null && pk.weight_grams !== '') {
|
||||
wCell = String(pk.weight_grams).replace('.', ',');
|
||||
}
|
||||
var row = $('<tr data-packaging-item-id="' + id + '">' +
|
||||
'<td class="text-muted align-middle packaging-drag-handle" style="cursor:grab">☰</td>' +
|
||||
'<td class="align-middle"></td><td class="align-middle"></td><td class="align-middle text-right"></td>' +
|
||||
'<td><input type="hidden" name="pp_packaging_item_id[]" value="' + id + '">' +
|
||||
'<input type="text" name="pp_quantity[]" class="form-control form-control-sm pp-quantity" value="1" autocomplete="off"></td>' +
|
||||
'<td class="align-middle"><a class="text-danger packaging-row-remove" href="#" title="{{ __('Entfernen') }}"><i class="far fa-trash-alt"></i></a></td></tr>');
|
||||
row.find('td').eq(1).text(pk.name || '');
|
||||
row.find('td').eq(2).text(pk.material_name || '—');
|
||||
row.find('td').eq(3).text(wCell);
|
||||
$('#packaging-sortable-rows').append(row);
|
||||
}
|
||||
|
||||
function syncPackagingModalRows() {
|
||||
var usedIds = {};
|
||||
$('#packaging-sortable-rows tr').each(function () {
|
||||
var pid = $(this).data('packaging-item-id');
|
||||
if (pid) {
|
||||
usedIds[String(pid)] = true;
|
||||
}
|
||||
});
|
||||
$('#modal-packaging-pick .packaging-modal-row').each(function () {
|
||||
var pid = String($(this).data('packaging-item-id'));
|
||||
var inUse = !!usedIds[pid];
|
||||
var $cb = $(this).find('.js-packaging-modal-cb');
|
||||
$cb.prop('disabled', inUse);
|
||||
$cb.attr('title', inUse ? '{{ __('Bereits im Produkt') }}' : '');
|
||||
if (inUse) {
|
||||
$cb.prop('checked', false);
|
||||
}
|
||||
$(this).toggleClass('text-muted', inUse);
|
||||
$(this).css('opacity', inUse ? 0.65 : 1);
|
||||
$(this).find('.js-pk-modal-name').toggleClass('text-muted', inUse);
|
||||
$(this).find('.js-pk-modal-mat').toggleClass('text-muted', inUse);
|
||||
});
|
||||
}
|
||||
|
||||
function filterPackagingModalRows() {
|
||||
var q = $('#packaging-modal-search').val().trim().toLowerCase();
|
||||
var visible = 0;
|
||||
$('#packaging-modal-tbody .packaging-modal-row').each(function () {
|
||||
var hay = $(this).attr('data-packaging-search') || '';
|
||||
var match = !q || hay.indexOf(q) !== -1;
|
||||
$(this).toggle(match);
|
||||
if (match) {
|
||||
visible++;
|
||||
}
|
||||
});
|
||||
$('#packaging-modal-no-results').toggleClass('d-none', !(q.length > 0 && visible === 0));
|
||||
}
|
||||
|
||||
$('#packaging-modal-search').on('input', filterPackagingModalRows);
|
||||
|
||||
$('#modal-packaging-pick').on('show.bs.modal', function () {
|
||||
$('#packaging-modal-search').val('');
|
||||
$('#packaging-modal-tbody .packaging-modal-row').show();
|
||||
$('#packaging-modal-no-results').addClass('d-none');
|
||||
syncPackagingModalRows();
|
||||
});
|
||||
|
||||
$('#btn-packaging-modal-add').on('click', function () {
|
||||
var added = 0;
|
||||
$('#modal-packaging-pick .js-packaging-modal-cb:checked:not(:disabled)').each(function () {
|
||||
var id = String($(this).val());
|
||||
appendPackagingRow(id);
|
||||
$(this).prop('checked', false);
|
||||
added++;
|
||||
});
|
||||
if (added > 0) {
|
||||
syncPackagingModalRows();
|
||||
$('#modal-packaging-pick').modal('hide');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.packaging-row-remove', function (ev) {
|
||||
ev.preventDefault();
|
||||
$(this).closest('tr').remove();
|
||||
syncPackagingModalRows();
|
||||
});
|
||||
|
||||
// === Hersteller-Rezeptur (Manufacturer) ===
|
||||
var $mfgTbody = document.getElementById('mfg-ingredient-sortable-rows');
|
||||
if ($mfgTbody && typeof Sortable !== 'undefined') {
|
||||
new Sortable($mfgTbody, {
|
||||
handle: '.mfg-ingredient-drag-handle',
|
||||
animation: 150,
|
||||
});
|
||||
}
|
||||
|
||||
function updateMfgEffective($row) {
|
||||
var g = parseDeNumber($row.find('.mfg-gram').val());
|
||||
var f = parseDeNumber($row.find('.mfg-factor').val());
|
||||
var $out = $row.find('.mfg-effective');
|
||||
if (!isNaN(g) && !isNaN(f)) {
|
||||
$out.text((g * f).toLocaleString('de-DE', { minimumFractionDigits: 3, maximumFractionDigits: 3 }));
|
||||
} else {
|
||||
$out.text('—');
|
||||
}
|
||||
}
|
||||
|
||||
function updateMfgRecipeTotal() {
|
||||
var total = 0, hasValue = false;
|
||||
$('#mfg-ingredient-sortable-rows tr').each(function () {
|
||||
var v = parseDeNumber($(this).find('.mfg-gram').val());
|
||||
if (!isNaN(v)) { total += v; hasValue = true; }
|
||||
});
|
||||
var $cell = $('#mfg-recipe-total-percent');
|
||||
if (!hasValue) { $cell.text('—').removeClass('text-danger text-success'); return; }
|
||||
$cell.text(total.toLocaleString('de-DE', { minimumFractionDigits: 3, maximumFractionDigits: 3 }) + ' %');
|
||||
if (Math.abs(total - 100) < 0.001) {
|
||||
$cell.removeClass('text-danger').addClass('text-success').attr('title', '');
|
||||
} else {
|
||||
$cell.removeClass('text-success').addClass('text-danger').attr('title', 'Die Gesamtrezeptur ergibt nicht 100 %!');
|
||||
}
|
||||
}
|
||||
|
||||
function refreshAllMfgEffectives() {
|
||||
$('#mfg-ingredient-sortable-rows tr').each(function () { updateMfgEffective($(this)); });
|
||||
updateMfgRecipeTotal();
|
||||
}
|
||||
|
||||
function appendMfgIngredientRow(id) {
|
||||
id = String(id);
|
||||
var ing = catalog[id];
|
||||
if (!ing) return;
|
||||
var factor = ing.default_factor != null ? String(ing.default_factor).replace('.', ',') : '1,10';
|
||||
var row = $('<tr data-ingredient-id="' + id + '">' +
|
||||
'<td class="text-muted align-middle mfg-ingredient-drag-handle" style="cursor:grab">☰</td>' +
|
||||
'<td class="align-middle"></td><td class="align-middle small text-muted"></td><td class="align-middle"></td>' +
|
||||
'<td><input type="hidden" name="mfg_ingredient_id[]" value="' + id + '">' +
|
||||
'<input type="text" name="mfg_gram[]" class="form-control form-control-sm mfg-gram" value="" autocomplete="off" step="0.001"></td>' +
|
||||
'<td><input type="text" name="mfg_factor[]" class="form-control form-control-sm mfg-factor" value="' + factor + '" autocomplete="off"></td>' +
|
||||
'<td class="align-middle mfg-effective text-right small text-muted">—</td>' +
|
||||
'<td class="align-middle"><a class="text-danger mfg-ingredient-row-remove" href="#" title="Entfernen"><i class="far fa-trash-alt"></i></a></td></tr>');
|
||||
row.find('td').eq(1).text(ing.name || '');
|
||||
row.find('td').eq(2).text(ing.quality_name || '—');
|
||||
row.find('td').eq(3).text(ing.inci || '');
|
||||
$('#mfg-ingredient-sortable-rows').append(row);
|
||||
updateMfgEffective(row);
|
||||
updateMfgRecipeTotal();
|
||||
}
|
||||
|
||||
function syncMfgIngredientModalRows() {
|
||||
var usedIds = {};
|
||||
$('#mfg-ingredient-sortable-rows tr').each(function () {
|
||||
var id = $(this).data('ingredient-id');
|
||||
if (id) usedIds[String(id)] = true;
|
||||
});
|
||||
$('#modal-mfg-ingredients-pick .mfg-ingredient-modal-row').each(function () {
|
||||
var id = String($(this).data('ingredient-id'));
|
||||
var inUse = !!usedIds[id];
|
||||
var $cb = $(this).find('.js-mfg-ingredient-modal-cb');
|
||||
$cb.prop('disabled', inUse);
|
||||
if (inUse) $cb.prop('checked', false);
|
||||
$(this).css('opacity', inUse ? 0.65 : 1);
|
||||
});
|
||||
}
|
||||
|
||||
$('#mfg-ingredient-modal-search').on('input', function () {
|
||||
var q = $(this).val().trim().toLowerCase();
|
||||
var visible = 0;
|
||||
$('#mfg-ingredient-modal-tbody .mfg-ingredient-modal-row').each(function () {
|
||||
var hay = $(this).attr('data-ingredient-search') || '';
|
||||
var match = !q || hay.indexOf(q) !== -1;
|
||||
$(this).toggle(match);
|
||||
if (match) visible++;
|
||||
});
|
||||
$('#mfg-ingredient-modal-no-results').toggleClass('d-none', !(q.length > 0 && visible === 0));
|
||||
});
|
||||
|
||||
$('#modal-mfg-ingredients-pick').on('show.bs.modal', function () {
|
||||
$('#mfg-ingredient-modal-search').val('');
|
||||
$('#mfg-ingredient-modal-tbody .mfg-ingredient-modal-row').show();
|
||||
$('#mfg-ingredient-modal-no-results').addClass('d-none');
|
||||
syncMfgIngredientModalRows();
|
||||
});
|
||||
|
||||
$('#btn-mfg-ingredients-modal-add').on('click', function () {
|
||||
var added = 0;
|
||||
$('#modal-mfg-ingredients-pick .js-mfg-ingredient-modal-cb:checked:not(:disabled)').each(function () {
|
||||
appendMfgIngredientRow(String($(this).val()));
|
||||
$(this).prop('checked', false);
|
||||
added++;
|
||||
});
|
||||
if (added > 0) { syncMfgIngredientModalRows(); $('#modal-mfg-ingredients-pick').modal('hide'); }
|
||||
});
|
||||
|
||||
$(document).on('click', '.mfg-ingredient-row-remove', function (ev) {
|
||||
ev.preventDefault();
|
||||
$(this).closest('tr').remove();
|
||||
syncMfgIngredientModalRows();
|
||||
updateMfgRecipeTotal();
|
||||
});
|
||||
|
||||
$(document).on('input change', '.mfg-gram, .mfg-factor', function () {
|
||||
updateMfgEffective($(this).closest('tr'));
|
||||
updateMfgRecipeTotal();
|
||||
});
|
||||
|
||||
refreshAllMfgEffectives();
|
||||
|
||||
function toggleShelfMonths() {
|
||||
var v = $('.js-shelf-life-type:checked').val();
|
||||
if (v === 'fixed') {
|
||||
$('#shelf-life-months-wrap').show();
|
||||
} else {
|
||||
$('#shelf-life-months-wrap').hide();
|
||||
}
|
||||
}
|
||||
$(document).on('change', '.js-shelf-life-type', toggleShelfMonths);
|
||||
toggleShelfMonths();
|
||||
});
|
||||
})(jQuery);
|
||||
</script>
|
||||
@endsection
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue