Skip to main content

Enhanced Label Printing for Ultimate POS

This guide demonstrates how to enhance the label printing functionality in Ultimate POS with improved UI/UX, optimized barcode generation, and better print capabilities.

Overview​

The enhanced label printing system provides:

  • Modern, responsive interface with toggle switches and size controls
  • Optimized barcode and QR code generation for better print quality
  • Enhanced preview with real-time updates
  • Label printer-optimized print functionality
  • Better image handling and currency formatting

Prerequisites: QR Code Support Setup​

Before using the enhanced label printing system with QR codes, you need to add QR code support to your barcode settings if it doesn't already exist.

Adding QR Code Barcode Setting​

If you want to add "For Qr Code" as a barcode setting option, run this SQL command in your database:

INSERT INTO `barcodes` VALUES (7,'For Qr Code',NULL,0.9000,0.9000,10.0000,10.0000,0.0000,0.0000,0.1000,0.1000,1,0,0,1,1,'2025-06-07 00:00:00','2025-06-07 00:00:00');

QR Code Barcode Setting Selection QR Code barcode setting "For Qr Code" available in the dropdown after running the SQL command

Enhanced Interface Preview​

Enhanced Label Interface with QR Codes Enhanced label printing interface showing QR code labels with modern toggle controls

The enhanced interface features:

  • Left Panel: Modern toggle switches for enabling/disabling label elements
  • Size Controls: Plus/minus buttons with numeric inputs for precise sizing
  • Real-time Preview: Instant preview updates as you adjust settings
  • Multiple Print Options: Standard printing, label printer optimization, and QR-only modes

Step 1: Add Routes​

Add the enhanced label routes to your routes/web.php file:

// Enhanced Print Labels Routes
Route::get('/labels/enhanced-show', [LabelsController::class, 'enhancedShow'])->name('labels.enhanced-show');
Route::get('/labels/enhanced-preview', [LabelsController::class, 'enhancedPreview'])->name('labels.enhanced-preview');

Step 2: Update Controller​

Add the following methods to your app/Http/Controllers/LabelsController.php:

Enhanced Show Method​

/**
* Enhanced label printing interface
*/
public function enhancedShow(Request $request)
{
$business_id = $request->session()->get('user.business_id');
$purchase_id = $request->get('purchase_id', false);
$product_id = $request->get('product_id', false);

//Get products for the business
$products = [];
$price_groups = [];
if ($purchase_id) {
$products = $this->transactionUtil->getPurchaseProducts($business_id, $purchase_id);
} elseif ($product_id) {
$products = $this->productUtil->getDetailsFromProduct($business_id, $product_id);
}

//get price groups
$price_groups = [];
if (! empty($purchase_id) || ! empty($product_id)) {
$price_groups = SellingPriceGroup::where('business_id', $business_id)
->active()
->pluck('name', 'id');
}

$barcode_settings = Barcode::where('business_id', $business_id)
->orWhereNull('business_id')
->select(DB::raw('CONCAT(name, ", ", COALESCE(description, "")) as name, id, is_default'))
->get();
$default = $barcode_settings->where('is_default', 1)->first();
$barcode_settings = $barcode_settings->pluck('name', 'id');

// Fetch the business logo path
$business_logo = request()->session()->get('business.logo');
if (! empty($business_logo)) {
$business_logo = asset('uploads/business_logos/' . $business_logo);
}

return view('labels.enhanced-show')
->with(compact('products', 'barcode_settings', 'default', 'price_groups', 'business_logo'));
}

/**
* Enhanced preview method (reuses existing logic)
*/
public function enhancedPreview(Request $request)
{
// Reuse the existing show_label_preview method
return $this->show_label_preview($request);
}

Enhanced Optimization Methods​

Add these optimization methods to improve barcode and QR code quality:

/**
* Generate optimized barcode with dynamic sizing
*/
private function generateOptimizedBarcode($data, $label_width_inches, $barcode_size_setting)
{
$width_factor = $this->calculateOptimalBarcodeWidth($label_width_inches);
$height_pixels = max(40, $barcode_size_setting * 40);

$dns1d = new DNS1D();
$barcode = $dns1d->getBarcodePNG(
$data,
'C128',
$width_factor,
$height_pixels,
[0, 0, 0],
true
);

return $barcode;
}

/**
* Calculate optimal barcode width factor based on label dimensions
*/
private function calculateOptimalBarcodeWidth($label_width_inches)
{
if ($label_width_inches <= 1.5) {
return 4; // Narrow labels
} elseif ($label_width_inches <= 2.5) {
return 6; // Medium labels
} else {
return 8; // Wide labels
}
}

/**
* Generate high-resolution QR code with error correction
*/
private function generateOptimizedQRCode($data, $size_setting)
{
$pixel_size = max(120, $size_setting * 80);

$dns2d = new DNS2D();
$qr_code = $dns2d->getBarcodePNG(
$data,
'QRCODE',
$pixel_size,
$pixel_size,
[0, 0, 0],
false,
3
);

return $qr_code;
}

/**
* Get label image URL from settings
*/
public function getLabelImageUrlFromSettings($business_id)
{
$business_logo = request()->session()->get('business.logo');
if (! empty($business_logo)) {
$business_logo = asset('uploads/business_logos/' . $business_logo);
}
return $business_logo;
}

Enhanced Preview Method​

Update your existing show_label_preview method to include the optimizations:

/**
* Enhanced show_label_preview method with optimizations
*/
public function show_label_preview(Request $request)
{
try {
$products = $request->get('products');
$print = $request->get('print');
$barcode_setting = $request->get('barcode_setting');
$business_id = $request->session()->get('user.business_id');

$barcode_details = Barcode::find($barcode_setting);
$barcode_details->stickers_in_one_sheet = $barcode_details->is_continuous ? $barcode_details->stickers_in_one_row : $barcode_details->stickers_in_one_sheet;
$barcode_details->paper_height = $barcode_details->is_continuous ? $barcode_details->height : $barcode_details->paper_height;

if ($barcode_details->stickers_in_one_row == 1) {
$barcode_details->col_distance = 0;
$barcode_details->row_distance = 0;
}

$business_name = $request->session()->get('business.name');

// Handle image source selection
$image_url = null;
if (!empty($print['image'])) {
if ($print['image_source'] === 'select_image' && !empty($print['select_image_url'])) {
$image_url = $print['select_image_url'];
} elseif ($print['image_source'] === 'label_image') {
$image_url = $this->getLabelImageUrlFromSettings($business_id);
}
}

$product_details_page_wise = [];
$total_qty = 0;

foreach ($products as $value) {
$details = $this->productUtil->getDetailsFromVariation($value['variation_id'], $business_id, null, false);

// Format prices properly
$details->sell_price_inc_tax = $this->productUtil->num_f($details->sell_price_inc_tax) ?: $details->sell_price_inc_tax;
$details->default_sell_price = $this->productUtil->num_f($details->default_sell_price) ?: $details->default_sell_price;

if (!empty($value['exp_date'])) {
$details->exp_date = $value['exp_date'];
}
if (!empty($value['packing_date'])) {
$details->packing_date = $value['packing_date'];
}
if (!empty($value['lot_number'])) {
$details->lot_number = $value['lot_number'];
}

if (!empty($value['price_group_id'])) {
$tax_id = $print['price_type'] == 'inclusive' ?: $details->tax_id;
$group_prices = $this->productUtil->getVariationGroupPrice($value['variation_id'], $value['price_group_id'], $tax_id);
$details->sell_price_inc_tax = $group_prices['price_inc_tax'];
$details->default_sell_price = $group_prices['price_exc_tax'];
}

for ($i = 0; $i < $value['quantity']; $i++) {
$page = intdiv($total_qty, $barcode_details->stickers_in_one_sheet);

if ($total_qty % $barcode_details->stickers_in_one_sheet == 0) {
$product_details_page_wise[$page] = [];
}

// OPTIMIZATION: Pre-generate optimized barcodes and QR codes
if (!empty($print['barcode'])) {
$details->optimized_barcode = $this->generateOptimizedBarcode(
$details->sub_sku,
$barcode_details->width,
$print['barcode_size'] ?? 0.8
);
}

if (!empty($print['qrcode'])) {
$details->optimized_qrcode = $this->generateOptimizedQRCode(
$details->sub_sku,
$print['qrcode_size'] ?? 1.4
);
}

$product_details_page_wise[$page][] = $details;
$total_qty++;
}
}

$margin_top = $barcode_details->is_continuous ? 0 : $barcode_details->top_margin * 1;
$margin_left = $barcode_details->is_continuous ? 0 : $barcode_details->left_margin * 1;
$paper_width = $barcode_details->paper_width * 1;
$paper_height = $barcode_details->paper_height * 1;

$i = 0;
$len = count($product_details_page_wise);
$is_first = false;
$is_last = false;

$factor = (($barcode_details->width / $barcode_details->height)) / ($barcode_details->is_continuous ? 2 : 4);
$html = '';

foreach ($product_details_page_wise as $page => $page_products) {
$output = view('labels.partials.enhanced_preview') // Use enhanced template
->with(compact('print', 'page_products', 'business_name', 'barcode_details', 'margin_top', 'margin_left', 'paper_width', 'paper_height', 'is_first', 'is_last', 'factor', 'image_url'))
->render();

$html .= $output;
}

return response()->json(['success' => true, 'html' => $html]);
} catch (\Exception $e) {
\Log::emergency('File:' . $e->getFile() . 'Line:' . $e->getLine() . 'Message:' . $e->getMessage());

return response()->json([
'success' => false,
'msg' => __('lang_v1.barcode_label_error')
]);
}
}

Step 3: Add Menu Item​

Add the enhanced labels menu item to app/Http/Middleware/AdminSidebarMenu.php under the existing labels menu:

if (auth()->user()->can('product.view')) {
$sub->url(
action([\App\Http\Controllers\LabelsController::class, 'show']),
__('barcode.print_labels'),
['icon' => '', 'active' => request()->segment(1) == 'labels' && request()->segment(2) == 'show']
);

// Add enhanced version
$sub->url(
action([\App\Http\Controllers\LabelsController::class, 'enhancedShow']),
__('barcode.print_labels') . ' (Enhanced)',
['icon' => '', 'active' => request()->segment(1) == 'labels' && request()->segment(2) == 'enhanced-show']
);
}

Step 4: Create Enhanced Views​

Main Enhanced View​

Create resources/views/labels/enhanced-show.blade.php:

@extends('layouts.app')
@section('title', __('barcode.print_labels'))

@section('css')
<!-- Enhanced Labels CSS -->
<link rel="stylesheet" href="{{ asset('css/enhanced-labels.css?v=' . $asset_v) }}">
@endsection

@section('content')
<!-- Enhanced Label Printing Interface -->
<div class="enhanced-labels-container">
<!-- Header -->
<div class="enhanced-header">
<div class="enhanced-header-content">
<div class="enhanced-header-inner">
<h1 class="enhanced-title">@lang('barcode.print_labels')</h1>
<p class="enhanced-subtitle">Enhanced interface for generating product labels</p>
</div>
</div>
</div>

<!-- Main Content -->
<div class="enhanced-main">
{!! Form::open(['url' => '#', 'method' => 'post', 'id' => 'preview_setting_form', 'onsubmit' => 'return false'])
!!}

<!-- Product Search Section -->
<div class="enhanced-card">
<div class="enhanced-card-header">
<h2 class="enhanced-card-title">@lang('product.add_product_for_labels')</h2>
</div>
<div class="enhanced-card-content">
<div class="enhanced-input-group">
<div class="enhanced-input-icon">
<i class="fas fa-search"></i>
</div>
{!! Form::text('search_product', null, [
'class' => 'enhanced-input',
'id' => 'search_product_for_label',
'placeholder' => __('lang_v1.enter_product_name_to_print_labels'),
'autofocus'
]); !!}
</div>

<!-- Products Table -->
<div class="enhanced-table-container">
<table class="enhanced-table" id="product_table">
<thead>
<tr>
<th>@lang('barcode.products')</th>
<th>@lang('barcode.no_of_labels')</th>
@if(request()->session()->get('business.enable_lot_number') == 1)
<th>@lang('lang_v1.lot_number')</th>
@endif
@if(request()->session()->get('business.enable_product_expiry') == 1)
<th>@lang('product.exp_date')</th>
@endif
<th>@lang('lang_v1.packing_date')</th>
<th>@lang('lang_v1.selling_price_group')</th>
</tr>
</thead>
<tbody>
@include('labels.partials.show_table_rows', ['index' => 0])
</tbody>
</table>
</div>
</div>
</div>

<!-- Two Column Layout -->
<div class="enhanced-grid">

<!-- Left Column - Settings Panel -->
<div class="enhanced-card">
<div class="enhanced-card-header">
<h2 class="enhanced-card-title">Label Settings</h2>
</div>
<div class="enhanced-card-content">

<!-- Barcode Setting -->
<div class="enhanced-input-group" style="max-width: 100%;">
<label class="nested-label">@lang('barcode.barcode_setting')</label>
<div style="position: relative;">
<div class="enhanced-input-icon">
<i class="fas fa-cog"></i>
</div>
{!! Form::select('barcode_setting', $barcode_settings, !empty($default) ? $default->id :
null, [
'class' => 'enhanced-select',
'onchange' => 'fetchPreview()'
]); !!}
</div>
</div>

<!-- Label Elements -->
<div class="label-elements">
<h3 class="label-elements-title">Label Elements</h3>

<!-- Business Image -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[image]" value="1" class="hidden label-toggle">
<div class="toggle-switch"></div>
<span class="toggle-label-text">Business Image</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[image_size]', -1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[image_size]" value="25" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[image_size]', 1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">px</span>
</div>
</div>

<!-- Image Source Selection -->
<div class="nested-controls">
<div class="nested-input-group">
<label class="nested-label">Image Source</label>
<select name="print[image_source]" id="image_source" onchange="toggleImageSource()"
class="nested-select">
<option value="select_image">Select Image URL</option>
<option value="label_image">Label Image</option>
</select>
</div>
<div id="select_image_url_row" class="nested-input-group hidden">
<input type="text" name="print[select_image_url]" placeholder="Enter image URL"
class="nested-input" onchange="fetchPreview()">
</div>
</div>

<!-- Barcode -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[barcode]" value="1" checked
class="hidden label-toggle">
<div class="toggle-switch active"></div>
<span class="toggle-label-text">@lang('barcode.barcodes')</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[barcode_size]', -0.1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[barcode_size]" value="0.8" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[barcode_size]', 0.1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">in</span>
</div>
</div>

<!-- QR Code -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[qrcode]" value="1" class="hidden label-toggle">
<div class="toggle-switch"></div>
<span class="toggle-label-text">@lang('barcode.print_qrcode')</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[qrcode_size]', -0.1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[qrcode_size]" value="1.4" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[qrcode_size]', 0.1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">in</span>
</div>
</div>

<!-- Product Name -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[name]" value="1" checked class="hidden label-toggle">
<div class="toggle-switch active"></div>
<span class="toggle-label-text">@lang('barcode.print_name')</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[name_size]', -1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[name_size]" value="11" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[name_size]', 1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">px</span>
</div>
</div>

<!-- SKU -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[sku]" value="1" class="hidden label-toggle">
<div class="toggle-switch"></div>
<span class="toggle-label-text">@lang('product.sku')</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[sku_size]', -1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[sku_size]" value="11" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[sku_size]', 1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">px</span>
</div>
</div>

<!-- Variations -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[variations]" value="1" class="hidden label-toggle">
<div class="toggle-switch"></div>
<span class="toggle-label-text">@lang('barcode.print_variations')</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[variations_size]', -1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[variations_size]" value="17" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[variations_size]', 1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">px</span>
</div>
</div>

<!-- Price -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[price]" value="1" id="is_show_price"
class="hidden label-toggle">
<div class="toggle-switch"></div>
<span class="toggle-label-text">@lang('barcode.print_price')</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[price_size]', -1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[price_size]" value="9" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[price_size]', 1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">px</span>
</div>
</div>

<!-- Price Type Selection -->
<div id="price_type_div" class="nested-controls hidden">
<div class="nested-input-group">
<label class="nested-label">Price Type</label>
<select name="print[price_type]" class="nested-select" onchange="fetchPreview()">
<option value="exclusive">Exclusive of Tax</option>
<option value="inclusive">Inclusive of Tax</option>
</select>
</div>
</div>

<!-- Business Name -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[business_name]" value="1"
class="hidden label-toggle">
<div class="toggle-switch"></div>
<span class="toggle-label-text">@lang('barcode.print_business_name')</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[business_name_size]', -1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[business_name_size]" value="20" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[business_name_size]', 1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">px</span>
</div>
</div>

<!-- Packing Date -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[packing_date]" value="1" class="hidden label-toggle">
<div class="toggle-switch"></div>
<span class="toggle-label-text">@lang('lang_v1.print_packing_date')</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[packing_date_size]', -1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[packing_date_size]" value="12" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[packing_date_size]', 1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">px</span>
</div>
</div>

@if(request()->session()->get('business.enable_lot_number') == 1)
<!-- Lot Number -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[lot_number]" value="1" class="hidden label-toggle">
<div class="toggle-switch"></div>
<span class="toggle-label-text">@lang('lang_v1.print_lot_number')</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[lot_number_size]', -1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[lot_number_size]" value="12" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[lot_number_size]', 1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">px</span>
</div>
</div>
@endif

@if(request()->session()->get('business.enable_product_expiry') == 1)
<!-- Expiry Date -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[exp_date]" value="1" class="hidden label-toggle">
<div class="toggle-switch"></div>
<span class="toggle-label-text">@lang('lang_v1.print_exp_date')</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[exp_date_size]', -1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[exp_date_size]" value="12" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[exp_date_size]', 1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">px</span>
</div>
</div>
@endif

@php
$custom_labels = json_decode(session('business.custom_labels'), true);
$product_custom_fields = !empty($custom_labels['product']) ? $custom_labels['product'] : [];
@endphp
@foreach($product_custom_fields as $index => $cf)
@if(!empty($cf))
@php
$field_name = 'product_custom_field' . $loop->iteration;
@endphp
<!-- Custom Field: {{ $cf }} -->
<div class="toggle-container">
<div class="toggle-label-wrapper">
<input type="checkbox" name="print[{{ $field_name }}]" value="1"
class="hidden label-toggle">
<div class="toggle-switch"></div>
<span class="toggle-label-text">{{ $cf }}</span>
</div>
<div class="size-controls">
<button type="button" onclick="updateInputSize('print[{{ $field_name }}_size]', -1)"
class="size-btn size-btn-minus">-</button>
<input type="text" name="print[{{ $field_name }}_size]" value="12" class="size-input"
onchange="fetchPreview()">
<button type="button" onclick="updateInputSize('print[{{ $field_name }}_size]', 1)"
class="size-btn size-btn-plus">+</button>
<span class="size-unit">px</span>
</div>
</div>
@endif
@endforeach
</div>
</div>
</div>

<!-- Right Column - Preview Panel -->
<div class="enhanced-card">
<div class="enhanced-card-header">
<div class="preview-header">
<h2 class="enhanced-card-title">Label Preview</h2>
<div class="preview-buttons">
<!-- Original print button -->
<button type="button" id="print_label" class="btn-primary">
<i class="fas fa-print"></i>
Print Label
</button>

<!-- Enhanced print button -->
<button type="button" id="enhanced_print_label" class="btn-success">
<i class="fas fa-tag"></i>
Label Printer
</button>

<!-- QR Code button -->
<button type="button" id="labels_preview_qr" class="btn-secondary">
<i class="fas fa-qrcode"></i>
QR Code
</button>
</div>
</div>
</div>

<!-- Preview Container -->
<div class="enhanced-card-content">
<!-- Info container outside preview box -->
<div id="label_info_container" class="mb-3">
<!-- Info will be populated by JavaScript -->
</div>

<div id="preview_box">
<div class="preview-empty">
<div class="preview-empty-content">
<i class="fas fa-eye preview-empty-icon"></i>
<p>Add products to see preview</p>
</div>
</div>
</div>
</div>
</div>
</div>

{!! Form::close() !!}
</div>
</div>

@endsection

@section('javascript')
<!-- Keep your existing labels.js -->
<script src="{{ asset('js/labels.js?v=' . $asset_v) }}"></script>

<!-- Add the new enhancement file -->
<script src="{{ asset('js/labels-enhancements.js?v=' . $asset_v) }}"></script>

<script>
$(document).ready(function() {
// Initialize toggle switches
initializeToggleSwitches();

// Initialize image source functionality
initializeImageSource();

// Initialize price type toggle
initializePriceToggle();
});

// Toggle switch functionality
function initializeToggleSwitches() {
$('.label-toggle').each(function() {
const $checkbox = $(this);
const $toggleSwitch = $checkbox.siblings('.toggle-switch');

// Set initial state
if ($checkbox.is(':checked')) {
$toggleSwitch.addClass('active');
}

// Handle toggle click
$toggleSwitch.on('click', function() {
$checkbox.prop('checked', !$checkbox.is(':checked'));
$(this).toggleClass('active', $checkbox.is(':checked'));
fetchPreview();
});
});
}

// Image source functionality
function initializeImageSource() {
toggleImageSource(); // Set initial state
}

function toggleImageSource() {
const selectedSource = $('select[name="print[image_source]"]').val();
if (selectedSource === 'select_image') {
$('#select_image_url_row').removeClass('hidden').addClass('block');
} else {
$('#select_image_url_row').removeClass('block').addClass('hidden');
}
fetchPreview();
}

// Price type toggle
function initializePriceToggle() {
const $priceToggle = $('#is_show_price');
const $priceTypeDiv = $('#price_type_div');

function togglePriceType() {
if ($priceToggle.is(':checked')) {
$priceTypeDiv.removeClass('hidden').addClass('block');
} else {
$priceTypeDiv.removeClass('block').addClass('hidden');
}
fetchPreview();
}

// Set initial state
togglePriceType();

// Listen for changes
$priceToggle.on('change', togglePriceType);
}

// Enhanced size update function
function updateInputSize(inputName, step) {
const $inputField = $(`input[name="${inputName}"]`);
let currentValue = parseFloat($inputField.val()) || 0;

currentValue += step;

// Minimum value protection
if (currentValue < 0.1) {
currentValue = 0.1;
}

// Format the value
if (currentValue % 1 === 0) {
$inputField.val(currentValue.toFixed(0));
} else {
$inputField.val(currentValue.toFixed(1));
}

fetchPreview();
}

// Enhanced fetchPreview function that uses the enhanced endpoint
function fetchPreview() {
if ($('form#preview_setting_form table#product_table tbody tr').length === 0) {
$('#preview_box').html(`
<div class="preview-empty">
<div class="preview-empty-content">
<i class="fas fa-eye preview-empty-icon"></i>
<p>Add products to see preview</p>
</div>
</div>
`);
$('#print_label, #enhanced_print_label').prop('disabled', false).removeClass('opacity-50');
return;
}

// Disable print buttons while loading
$('#print_label, #enhanced_print_label').prop('disabled', true).addClass('opacity-50');

// Show loading state
$('#preview_box').html(`
<div class="preview-empty">
<div class="preview-empty-content">
<div class="loading-spinner"></div>
<p>Generating preview...</p>
</div>
</div>
`);

const formData = $('#preview_setting_form').serialize();

$.ajax({
url: '{{ url("/labels/enhanced-preview") }}',
type: 'GET',
data: formData,
success: function(response) {
if (response.success) {
$('#preview_box').html(response.html);
__currency_convert_recursively($('#preview_box'));

// Auto-adjust preview scale for multiple labels
if (typeof adjustPreviewScale === 'function') {
setTimeout(function() {
adjustPreviewScale();
$('#print_label, #enhanced_print_label').prop('disabled', false).removeClass('opacity-50');
}, 100);
} else {
$('#print_label, #enhanced_print_label').prop('disabled', false).removeClass('opacity-50');
}
} else if (response.error) {
$('#preview_box').html('<div class="text-red-500 text-center p-4">' + response.error + '</div>');
$('#print_label, #enhanced_print_label').prop('disabled', false).removeClass('opacity-50');
}
},
error: function(xhr, status, error) {
console.error('AJAX Error:', error);
$('#preview_box').html('<div class="text-red-500 text-center p-4">Error generating preview</div>');
$('#print_label, #enhanced_print_label').prop('disabled', false).removeClass('opacity-50');
}
});
}
</script>

@endsection

Enhanced Preview Template​

Create resources/views/labels/partials/enhanced_preview.blade.php:

<table
style="padding: 2px; margin: 0 auto; border-spacing: {{$barcode_details->col_distance * 1}}in {{$barcode_details->row_distance * 1}}in;">
@foreach($page_products as $page_product)
@if($loop->index % $barcode_details->stickers_in_one_row == 0)
<!-- create a new row -->
<tr>
@endif
<td align="center" valign="center" style="padding: 2px; border: 1px dotted lightgray;">
<div
style="display: flex; flex-direction: column; align-items: center; text-align: center; margin: 0; padding: 0; width: {{$barcode_details->width * 1}}in; height: {{$barcode_details->height * 1}}in; justify-content: center;">

<!-- Business Name -->
@if(!empty($print['business_name']))
<b style="font-size: {{ $print['business_name_size'] - 2 }}px; margin-bottom: 1px;">{{ $business_name
}}</b>
@endif

<!-- Product Name -->
@if(!empty($print['name']))
<span style="font-size: {{ $print['name_size'] - 2 }}px; margin-bottom: 1px;">
{{ $page_product->product_actual_name }}
@if(!empty($print['lot_number']) && !empty($page_product->lot_number))
<span style="font-size: 8px; margin-left: 2px;">
({{ $page_product->lot_number }})
</span>
@endif
</span>
@endif

<!-- Variation -->
@if(!empty($print['variations']) && $page_product->is_dummy != 1)
<span style="font-size: {{$print['variations_size']}}px; margin-bottom: 4px;">
{{$page_product->product_variation_name}}: <b>{{$page_product->variation_name}}</b>
</span>
@endif

<!-- Custom Fields -->
@php
$custom_labels = json_decode(session('business.custom_labels'), true);
$product_custom_fields = !empty($custom_labels['product']) ? $custom_labels['product'] : [];
@endphp
@foreach($product_custom_fields as $index => $cf)
@php
$field_name = 'product_custom_field' . $loop->iteration;
@endphp
@if(!empty($cf) && !empty($page_product->$field_name) && !empty($print[$field_name]))
<span style="font-size: {{ $print[$field_name . '_size'] }}px; margin-bottom: 4px;">
<b>{{ $cf }}:</b> {{ $page_product->$field_name }}
</span>
@endif
@endforeach

<!-- Enhanced Price with Proper Currency Placement -->
@if(!empty($print['price']))
<span style="font-size: {{ $print['price_size'] ?? 10 }}px; margin-bottom: 1px;">
@lang('lang_v1.price'):
<b>
@php
$currency_symbol = session('currency')['symbol'] ?? '';
$currency_placement = session('business.currency_symbol_placement') ?? 'before';

if(!empty($print['price_type']) && $print['price_type'] == 'inclusive') {
$price_amount = $page_product->sell_price_inc_tax;
} else {
$price_amount = $page_product->default_sell_price;
}
@endphp

@if($currency_placement === 'after')
{{ $price_amount }} {{ $currency_symbol }}
@else
{{ $currency_symbol }} {{ $price_amount }}
@endif
</b>
</span>
@endif

<!-- Expiry Date -->
@if(!empty($print['exp_date']) && !empty($page_product->exp_date))
<span style="font-size: {{$print['exp_date_size']}}px; margin-bottom: 4px;">
<b>@lang('product.exp_date'):</b> {{$page_product->exp_date}}
</span>
@endif

<!-- Packing Date -->
@if(!empty($print['packing_date']) && !empty($page_product->packing_date))
<span style="font-size: {{$print['packing_date_size']}}px; margin-bottom: 4px;">
<b>@lang('lang_v1.packing_date'):</b> {{$page_product->packing_date}}
</span>
@endif

<!-- Enhanced Image & Barcode Section -->
<div style="display: flex; justify-content: center; align-items: center; margin-top: 2px;">
<!-- Small Image -->
@if (!empty($print['image']) && !empty($image_url))
<img src="{{ $image_url }}" alt="Label Image"
style="width: {{ $print['image_size'] }}px; height: {{ $print['image_size'] }}px; margin-bottom: 1px;">
@endif

<!-- Enhanced Barcode: Wide and high quality -->
@if(!empty($print['barcode']))
@php
// Simple inline calculations that work
$barcode_width_factor = 5; // Better quality than default 1
$barcode_height = max(40, ($print['barcode_size'] ?? 0.8) * 50);
@endphp
<img style="width: 85%; height: {{$barcode_height}}px; margin-bottom: 1px; image-rendering: crisp-edges;"
src="data:image/png;base64,{{DNS1D::getBarcodePNG($page_product->sub_sku, 'C128', $barcode_width_factor, $barcode_height, [0, 0, 0], false)}}">
@endif

<!-- Enhanced QR Code: High resolution -->
@if(!empty($print['qrcode']))
@php
// High resolution for clarity
$qr_size = max(120, ($print['qrcode_size'] ?? 1.4) * 80);
$qr_display_size = ($print['qrcode_size'] ?? 1.4) * 72;
@endphp
<img style="width: {{$qr_display_size}}px; height: {{$qr_display_size}}px; margin-top: 1px; margin-left: 4px; image-rendering: crisp-edges;"
src="data:image/png;base64,{{ DNS2D::getBarcodePNG($page_product->sub_sku, 'QRCODE', $qr_size, $qr_size) }}">
@endif
</div>

<!-- SKU -->
@if(!empty($print['sku']))
<span style="font-size: {{$print['sku_size']}}px; margin-top: 1px;">
{{$page_product->sub_sku}}
</span>
@endif
</div>
</td>
@if($loop->iteration % $barcode_details->stickers_in_one_row == 0)
</tr>
@endif
@endforeach
</table>

<style type="text/css">
/* Enhanced CSS for Better Print Quality */
img {
image-rendering: crisp-edges;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-optimize-contrast;
-webkit-print-color-adjust: exact;
color-adjust: exact;
print-color-adjust: exact;
}

@media print {
table {
page-break-after: always;
}

@page {
size: {{ $paper_width }}in {{ $paper_height }}in;
margin: {{ $margin_top }}in {{ $margin_left }}in;
}

* {
-webkit-print-color-adjust: exact !important;
color-adjust: exact !important;
print-color-adjust: exact !important;
}
}
</style>

Step 5: Create Enhanced Assets​

Enhanced CSS Stylesheet​

Create public/css/enhanced-labels.css:

/* Enhanced Labels CSS - Clean Version */

/* =================================================================
BASE LAYOUT & STRUCTURE
================================================================= */

.enhanced-labels-container {
min-height: 100vh;
background-color: #f9fafb;
}

.enhanced-header {
background-color: white;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
border-bottom: 1px solid #e5e7eb;
}

.enhanced-header-content {
max-width: 1280px;
margin: 0 auto;
padding: 0 1rem;
}

.enhanced-header-inner {
padding: 1rem 0;
}

.enhanced-title {
font-size: 1.5rem;
font-weight: 700;
color: #111827;
margin: 0;
}

.enhanced-subtitle {
font-size: 0.875rem;
color: #6b7280;
margin: 0.25rem 0 0 0;
}

.enhanced-main {
max-width: 1280px;
margin: 0 auto;
padding: 1.5rem 1rem;
}

/* =================================================================
CARD SYSTEM
================================================================= */

.enhanced-card {
background-color: white;
border-radius: 0.5rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
border: 1px solid #e5e7eb;
margin-bottom: 1.5rem;
}

.enhanced-card-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid #e5e7eb;
}

.enhanced-card-title {
font-size: 1.125rem;
font-weight: 600;
color: #111827;
margin: 0;
}

.enhanced-card-content {
padding: 1.5rem;
}

/* =================================================================
GRID LAYOUT
================================================================= */

.enhanced-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}

@media (min-width: 1024px) {
.enhanced-grid {
grid-template-columns: 350px 1fr; /* Give preview more space */
}
}

/* =================================================================
FORM ELEMENTS
================================================================= */

.enhanced-input-group {
position: relative;
display: flex;
justify-content: center;
align-items: center;
margin: 0 auto 20px auto;
max-width: 500px;
}

.enhanced-input {
display: block;
width: 100%;
max-width: 400px;
padding: 0.75rem 1rem 0.75rem 2.5rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
transition: all 0.2s ease;
}

.enhanced-input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}

.enhanced-input-icon {
position: absolute;
top: 50%;
left: 0.75rem;
transform: translateY(-50%);
color: #9ca3af;
pointer-events: none;
z-index: 1;
}

.enhanced-select {
display: block;
width: 100%;
padding: 0.75rem 1rem 0.75rem 2.5rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
background-color: white;
font-size: 0.875rem;
}

.enhanced-select:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}

/* =================================================================
TABLE STYLES
================================================================= */

.enhanced-table-container {
margin-top: 1.5rem;
overflow: hidden;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
border-radius: 0.5rem;
}

.enhanced-table {
min-width: 100%;
border-collapse: separate;
border-spacing: 0;
}

.enhanced-table thead {
background-color: #f9fafb;
}

.enhanced-table th {
padding: 0.75rem 1.5rem;
text-align: left;
font-size: 0.75rem;
font-weight: 500;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e5e7eb;
}

.enhanced-table tbody {
background-color: white;
}

.enhanced-table td {
padding: 1rem 1.5rem;
border-bottom: 1px solid #e5e7eb;
}

.enhanced-table tbody tr:hover {
background-color: #f9fafb;
}

/* =================================================================
TOGGLE SWITCHES
================================================================= */

.toggle-container {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 0;
border-bottom: 1px solid #f3f4f6;
}

.toggle-label-wrapper {
display: flex;
align-items: center;
cursor: pointer;
}

.toggle-switch {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
background-color: #e5e7eb;
border-radius: 12px;
cursor: pointer;
transition: background-color 0.2s ease;
border: none;
outline: none;
}

.toggle-switch::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background-color: white;
border-radius: 50%;
transition: transform 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.toggle-switch.active {
background-color: #3b82f6;
}

.toggle-switch.active::after {
transform: translateX(20px);
}

.toggle-label-text {
margin-left: 0.75rem;
font-size: 0.875rem;
font-weight: 500;
color: #374151;
}

/* =================================================================
SIZE CONTROLS
================================================================= */

.size-controls {
display: flex;
align-items: center;
gap: 0;
}

.size-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: 1px solid #d1d5db;
background-color: white;
color: #374151;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
user-select: none;
}

.size-btn:hover {
background-color: #f9fafb;
border-color: #9ca3af;
}

.size-btn:active {
background-color: #f3f4f6;
}

.size-btn-minus {
border-radius: 4px 0 0 4px;
}

.size-btn-plus {
border-radius: 0 4px 4px 0;
}

.size-input {
width: 60px;
height: 28px;
border: 1px solid #d1d5db;
border-left: 0;
border-right: 0;
text-align: center;
font-size: 12px;
outline: none;
background-color: white;
}

.size-input:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 1px #3b82f6;
}

.size-unit {
font-size: 0.75rem;
color: #6b7280;
margin-left: 0.5rem;
}

/* =================================================================
LABEL ELEMENTS SECTION
================================================================= */

.label-elements {
margin-top: 1rem;
}

.label-elements-title {
font-size: 0.875rem;
font-weight: 500;
color: #111827;
margin-bottom: 1rem;
}

.nested-controls {
padding-left: 2rem;
margin-top: 0.75rem;
}

.nested-controls.hidden {
display: none;
}

.nested-controls.block {
display: block;
}

.nested-input-group {
margin-bottom: 0.75rem;
}

.nested-label {
display: block;
font-size: 0.75rem;
font-weight: 500;
color: #6b7280;
margin-bottom: 0.25rem;
}

.nested-input,
.nested-select {
display: block;
width: 100%;
font-size: 0.875rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
padding: 0.5rem 0.75rem;
}

.nested-input:focus,
.nested-select:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}

/* =================================================================
BUTTON STYLES
================================================================= */

.preview-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
}

.preview-buttons {
display: flex;
gap: 6px;
align-items: center;
}

.preview-buttons button {
min-width: auto;
white-space: nowrap;
padding: 4px 8px;
font-size: 11px;
font-weight: 500;
border-radius: 3px;
display: inline-flex;
align-items: center;
gap: 4px;
cursor: pointer;
transition: all 0.2s ease;
border: none;
line-height: 1.2;
height: 26px;
min-width: 80px;
justify-content: center;
}

.btn-primary {
color: white;
background-color: #3b82f6;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}

.btn-primary:hover {
background-color: #2563eb;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
}

.btn-success {
color: white;
background: linear-gradient(135deg, #10b981, #059669);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}

.btn-success:hover {
background: linear-gradient(135deg, #059669, #047857);
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(16, 185, 129, 0.3);
}

.btn-secondary {
color: #374151;
background-color: #f9fafb;
border: 1px solid #d1d5db;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.btn-secondary:hover {
background-color: #f3f4f6;
border-color: #9ca3af;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* =================================================================
PREVIEW CONTAINER
================================================================= */

#preview_box {
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
padding: 15px;
background-color: #f9fafb;
min-height: 400px;
max-height: 600px;
overflow: auto;
position: relative;
width: 100%;
box-sizing: border-box;
}

#preview_box table {
transform-origin: top left;
margin: 0;
border-collapse: separate;
table-layout: auto;
}

#preview_box td {
padding: 2px !important;
box-sizing: border-box;
vertical-align: top;
}

#preview_box img {
max-width: 100%;
height: auto;
}

/* =================================================================
PREVIEW STATES
================================================================= */

.preview-empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #6b7280;
}

.preview-empty-content {
text-align: center;
}

.preview-empty-icon {
font-size: 2.5rem;
margin-bottom: 0.75rem;
}

.loading-spinner {
display: inline-block;
width: 2rem;
height: 2rem;
border: 2px solid #e5e7eb;
border-radius: 50%;
border-top-color: #3b82f6;
animation: spin 1s ease-in-out infinite;
margin: 0 auto 0.75rem;
}

@keyframes spin {
to {
transform: rotate(360deg);
}
}

/* =================================================================
UTILITY CLASSES
================================================================= */

.hidden {
display: none !important;
}

.block {
display: block !important;
}

.text-center {
text-align: center !important;
}

.text-red-500 {
color: #ef4444;
}

.text-blue-600 {
color: #2563eb;
}

.text-gray-500 {
color: #6b7280;
}

/* Hide elements during printing */
.no-print,
.scroll-hint,
.preview-info {
display: block;
}

@media print {
.no-print,
.scroll-hint,
.preview-info {
display: none !important;
visibility: hidden !important;
}

/* Hide info message backgrounds by color */
div[style*='rgba(34, 197, 94'] {
display: none !important;
visibility: hidden !important;
}

div[style*='rgba(59, 130, 246'] {
display: none !important;
visibility: hidden !important;
}
}

/* =================================================================
RESPONSIVE DESIGN
================================================================= */

@media (max-width: 1023px) {
.enhanced-grid {
grid-template-columns: 1fr;
}

#preview_box {
max-height: 400px;
}

.preview-buttons {
flex-direction: column;
gap: 0.5rem;
}
}

@media (max-width: 640px) {
.enhanced-main {
padding: 1rem 0.5rem;
}

.enhanced-card-content {
padding: 1rem;
}

.toggle-container {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}

.size-controls {
margin-top: 0.5rem;
flex-wrap: wrap;
}

.preview-buttons {
width: 100%;
}

.preview-buttons button {
flex: 1;
justify-content: center;
}
}

#label_info_container {
margin-bottom: 10px;
}

#label_info_container:empty {
display: none;
}

Enhanced JavaScript File​

Create public/js/labels-enhancements.js:

/**
* Enhanced Labels Functionality
* This file adds enhanced features to the existing labels system
* without modifying the original labels.js file
*/

$(document).ready(function () {
// Initialize enhanced table event handlers
initializeEnhancedTableEvents();

// Initialize enhanced print functionality
initializeEnhancedPrintLabel();
});

/**
* Initialize event handlers for enhanced table inputs
* Uses existing fetchPreview() function from labels.js
*/
function initializeEnhancedTableEvents() {
// Handle quantity input changes
$(document).on(
'change input',
'table#product_table input[name*="[quantity]"]',
function () {
console.log('Enhanced: Quantity changed to:', $(this).val());
// Use existing fetchPreview function
if (typeof fetchPreview === 'function') {
fetchPreview();
}
}
);

// Handle packing date input changes
$(document).on(
'change',
'table#product_table input[name*="[packing_date]"]',
function () {
console.log('Enhanced: Packing date changed to:', $(this).val());
if (typeof fetchPreview === 'function') {
fetchPreview();
}
}
);

// Handle datepicker change event specifically for packing date
$(document).on(
'changeDate',
'table#product_table .label-date-picker[name*="[packing_date]"]',
function () {
console.log(
'Enhanced: Packing date changed via datepicker to:',
$(this).val()
);
if (typeof fetchPreview === 'function') {
fetchPreview();
}
}
);

// Handle price group selection changes
$(document).on(
'change',
'table#product_table select[name*="[price_group_id]"]',
function () {
console.log('Enhanced: Price group changed to:', $(this).val());
if (typeof fetchPreview === 'function') {
fetchPreview();
}
}
);

// Handle lot number changes (if lot numbers are enabled)
$(document).on(
'change',
'table#product_table input[name*="[lot_number]"]',
function () {
console.log('Enhanced: Lot number changed to:', $(this).val());
if (typeof fetchPreview === 'function') {
fetchPreview();
}
}
);

// Handle expiry date changes (if expiry dates are enabled)
$(document).on(
'change',
'table#product_table input[name*="[exp_date]"]',
function () {
console.log('Enhanced: Expiry date changed to:', $(this).val());
if (typeof fetchPreview === 'function') {
fetchPreview();
}
}
);

// Handle datepicker change event for expiry date
$(document).on(
'changeDate',
'table#product_table .label-date-picker[name*="[exp_date]"]',
function () {
console.log(
'Enhanced: Expiry date changed via datepicker to:',
$(this).val()
);
if (typeof fetchPreview === 'function') {
fetchPreview();
}
}
);

console.log('Enhanced table event handlers initialized');
}

/**
* BETTER APPROACH: Show label info without scaling
* Let content display at natural size with horizontal scroll
*/
function adjustPreviewScale() {
const $previewBox = $('#preview_box');
const $table = $previewBox.find('table');
const $infoContainer = $('#label_info_container'); // New separate container

if ($table.length === 0) {
$infoContainer.empty(); // Clear info when no table
return;
}

// Remove any previous hints from preview box only
$previewBox.find('.scroll-hint, .preview-info').remove();

// DON'T scale - let content display naturally
$table.css('transform', 'none');
$table.css('margin-bottom', '0');

// Wait for content to fully render
setTimeout(() => {
// Count actual label cells
const $labelCells = $table.find('td').filter(function () {
return (
$(this).find('div[style*="width:"], div[style*="height:"]')
.length > 0 ||
$(this).text().trim().length > 0 ||
$(this).find('img').length > 0
);
});

const totalLabels = $labelCells.length;
const $firstRow = $table.find('tr').first();
const columnsInFirstRow = $firstRow.find('td').filter(function () {
return (
$(this).find('div[style*="width:"], div[style*="height:"]')
.length > 0 ||
$(this).text().trim().length > 0 ||
$(this).find('img').length > 0
);
}).length;

const actualColumns = Math.max(1, columnsInFirstRow);
const actualRows = Math.max(1, Math.ceil(totalLabels / actualColumns));

// Check if content is wider than container
const containerWidth = $previewBox.width();
const tableWidth = $table[0].scrollWidth || $table.outerWidth();
const needsScroll = tableWidth > containerWidth;

console.log(
`Natural size: Container ${containerWidth}px, Table ${tableWidth}px, Needs scroll: ${needsScroll}`
);

// Show info in SEPARATE container (not in preview box)
let infoMessage = `<i class="fas fa-info-circle"></i> Showing ${totalLabels} labels (${actualColumns} x ${actualRows})`;

if (needsScroll) {
infoMessage += ` - Scroll horizontally to see all labels`;
}

// Put info in separate container, NOT in preview_box
$infoContainer.html(`
<div style="
background: rgba(59, 130, 246, 0.1);
color: #3b82f6;
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
text-align: center;
border: 1px solid rgba(59, 130, 246, 0.2);
">
${infoMessage}
</div>
`);

console.log(
`Showing ${totalLabels} labels at natural size - no scaling applied`
);
}, 300);
}

/**
* Enhanced Print Label Functionality - Optimized for Label Printers
* Uses button#enhanced_print_label instead of #print_label
* Optimized for small barcode/label printers
*/
function initializeEnhancedPrintLabel() {
$('button#enhanced_print_label').click(function () {
var printContents = $('#preview_box').html();

// Check if preview is still loading
if (
printContents.includes('Generating preview') ||
printContents.includes('loading-spinner')
) {
if (typeof toastr !== 'undefined') {
toastr.warning(
'Please wait for the preview to finish loading before printing.'
);
} else {
alert(
'Please wait for the preview to finish loading before printing.'
);
}
return;
}

if (printContents.trim().length > 0) {
// More robust cleaning of print content
var $tempDiv = $('<div>').html(printContents);

// Remove all info messages by class and content
$tempDiv.find('.no-print').remove();
$tempDiv.find('.scroll-hint').remove();
$tempDiv.find('.preview-info').remove();
$tempDiv.find('div:contains("Showing")').remove();
$tempDiv.find('div:contains("Scaled to")').remove();
$tempDiv.find('div:contains("labels")').remove();
$tempDiv.find('div:contains("Scroll horizontally")').remove();

// Additional cleanup - remove any divs with green/blue backgrounds (our info messages)
$tempDiv.find('div[style*="rgba(34, 197, 94"]').remove();
$tempDiv.find('div[style*="rgba(59, 130, 246"]').remove();

var cleanPrintContents = $tempDiv.html();

// Additional check for actual table content
if (
!cleanPrintContents.includes('<table') &&
!cleanPrintContents.includes('barcode')
) {
if (typeof toastr !== 'undefined') {
toastr.error(
'No labels found to print. Please wait for preview to load.'
);
} else {
alert(
'No labels found to print. Please wait for preview to load.'
);
}
return;
}

// Get dynamic label dimensions
var dimensions = getLabelDimensions();
var labelWidth = dimensions.width;
var labelHeight = dimensions.height;

console.log(
`Printing with dimensions: ${labelWidth}" x ${labelHeight}"`
);

// Open a new window optimized for label printing
var printWindow = window.open('', '_blank', 'width=400,height=300');
printWindow.document.open();

// Write label printer optimized HTML
printWindow.document.write(`
<html>
<head>
<title>Label Printer - Enhanced Print</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: 'Arial', sans-serif;
background: white;
font-size: 8px;
}

@media print {
/* Single label page setup */
@page {
size: 3in 2in; /* Larger size to ensure single page */
margin: 0.2in;
-webkit-print-color-adjust: exact;
color-adjust: exact;
}

body {
margin: 0;
padding: 0;
background: white !important;
font-family: Arial, sans-serif;
font-size: 10px;
line-height: 1.2;
}

/* Force single page layout */
* {
-webkit-print-color-adjust: exact !important;
color-adjust: exact !important;
print-color-adjust: exact !important;
page-break-inside: avoid !important;
break-inside: avoid !important;
}

/* Compact table layout */
table {
width: 100% !important;
border-collapse: collapse !important;
table-layout: fixed !important;
page-break-inside: avoid !important;
margin: 0 !important;
padding: 0 !important;
}

tr, td {
page-break-inside: avoid !important;
break-inside: avoid !important;
margin: 0 !important;
padding: 2px !important;
border: none !important;
vertical-align: top !important;
}

/* Compact content container */
div[style*="width:"][style*="height:"] {
display: block !important;
width: auto !important;
height: auto !important;
max-width: 100% !important;
padding: 5px !important;
margin: 0 !important;
text-align: center !important;
page-break-inside: avoid !important;
}

/* Text optimization */
span, b, div {
font-size: 8px !important;
line-height: 1.1 !important;
margin: 1px 0 !important;
padding: 0 !important;
display: block !important;
text-align: center !important;
}

/* Business name larger */
b {
font-size: 10px !important;
font-weight: bold !important;
}

/* Barcode container */
div[style*="flex"] {
display: block !important;
text-align: center !important;
margin: 2px 0 !important;
page-break-inside: avoid !important;
}

/* High-quality image rendering */
img {
image-rendering: crisp-edges !important;
image-rendering: -moz-crisp-edges !important;
image-rendering: -webkit-optimize-contrast !important;
-webkit-print-color-adjust: exact !important;
display: block !important;
margin: 2px auto !important;
max-width: 90% !important;
}

/* Barcode specific */
img[src*="barcode"], img[src*="DNS1D"] {
height: 25px !important;
width: auto !important;
max-width: 80% !important;
}

/* QR Code specific */
img[src*="qrcode"], img[src*="DNS2D"] {
width: 30px !important;
height: 30px !important;
}

/* Remove dotted borders */
td[style*="dotted"] {
border: none !important;
}
}

@media screen {
body {
padding: 20px;
background: #f5f5f5;
}

/* Show preview scaled up for screen viewing */
table {
transform: scale(1.5);
transform-origin: top left;
margin-bottom: 50px;
background: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
}
</style>
</head>
<body>
${cleanPrintContents}
</body>
</html>
`);

printWindow.document.close();

// Label printer specific print handling
printWindow.onload = function () {
// Small delay for label printers to process
setTimeout(function () {
printWindow.print();
// Keep window open a bit longer for label printers
setTimeout(function () {
printWindow.close();
}, 1000);
}, 500);
};

console.log('Label printer optimized print dialog opened');
} else {
if (typeof toastr !== 'undefined') {
toastr.error('No label preview available to print.');
} else {
alert('No label preview available to print.');
}
}
});

console.log('Label printer enhanced functionality initialized');
}

/**
* Get Label Dimensions from Barcode Settings
* Attempts to read the selected barcode setting dimensions
*/
function getLabelDimensions() {
// Try to get dimensions from barcode settings dropdown
var selectedOption = $('select[name="barcode_setting"] option:selected');
var defaultWidth = 1.1811; // 30mm fallback
var defaultHeight = 0.5906; // 15mm fallback

// Use custom size if set via setLabelPrinterSize()
if (window.labelPrinterWidth && window.labelPrinterHeight) {
return {
width: window.labelPrinterWidth,
height: window.labelPrinterHeight,
};
}

// TODO: Add logic to extract dimensions from barcode settings
// This would require checking your barcode_settings data structure

return {
width: defaultWidth,
height: defaultHeight,
};
}

/**
* Optional: Dynamic Label Size Configuration
* Call this function to set custom label dimensions
*/
function setLabelPrinterSize(widthInches, heightInches) {
window.labelPrinterWidth = widthInches || 1.1811; // Default 30mm
window.labelPrinterHeight = heightInches || 0.5906; // Default 15mm
console.log(
`Label printer size set to: ${window.labelPrinterWidth}" x ${window.labelPrinterHeight}"`
);
}

Step 6: Update Existing JavaScript​

Update your existing public/js/labels.js file by adding the fetchPreview() call in the get_label_product_row function:

function get_label_product_row(product_id, variation_id) {
if (product_id) {
var row_count = $('table#product_table tbody tr').length;
$.ajax({
method: 'GET',
url: '/labels/add-product-row',
dataType: 'html',
data: {
product_id: product_id,
row_count: row_count,
variation_id: variation_id,
},
success: function (result) {
// Append the new row to the table
$('table#product_table tbody').append(result);

// Initialize date pickers for any new date input fields
$('table#product_table tbody')
.find('.label-date-picker')
.each(function () {
$(this).datepicker({
autoclose: true,
});
});

// Call fetchPreview to update the preview after the row is added
fetchPreview(); // <-- ADD THIS LINE
},
error: function (xhr, status, error) {
console.error('Error adding product row:', error);
},
});
}
}

Step 7: Import Dependencies​

Ensure you have the necessary dependencies for barcode generation in your composer.json:

{
"require": {
"milon/barcode": "^10.0"
}
}

Run composer install to install the dependencies if not already installed.

Features & Benefits​

Enhanced User Interface​

  • Modern Design: Clean, responsive interface with card-based layout
  • Toggle Switches: Intuitive on/off controls for label elements
  • Size Controls: Plus/minus buttons for easy size adjustments
  • Real-time Preview: Instant preview updates as you make changes

Optimized Print Quality​

  • High-Resolution Barcodes: Dynamic sizing based on label dimensions
  • Enhanced QR Codes: Improved error correction and resolution
  • Label Printer Support: Specialized print mode for thermal label printers
  • Better Image Rendering: Crisp edges and optimized print styles

Advanced Functionality​

  • Multiple Image Sources: Choose between business logo or custom URL
  • Currency Formatting: Proper currency symbol placement
  • Custom Fields Support: Automatic handling of custom product fields
  • Responsive Design: Works on desktop, tablet, and mobile devices

Usage​

  1. Access Enhanced Labels: Navigate to Product > Print Labels (Enhanced) in the sidebar menu

  2. Add Products: Use the search field to find and add products to your label batch

  3. Configure Settings:

    • Select barcode format from dropdown
    • Toggle label elements on/off using switches
    • Adjust sizes using +/- controls
    • Choose image source if needed
  4. Preview & Print:

    • View real-time preview on the right
    • Use "Print Label" for standard printing
    • Use "Label Printer" for thermal label printers
    • Use "QR Code" for QR code-only labels

Barcode Labels Example​

Enhanced Label Interface with Barcodes Enhanced interface showing traditional barcode labels with business name, product details, and pricing

The screenshot above demonstrates:

  • Barcode Output: High-quality traditional barcodes with "ERP" business name display
  • Product Information: Clean layout with product names, prices, and SKUs
  • Grid Layout: Organized 5x10 label grid showing 50 labels efficiently
  • Toggle Controls: Easy switching between barcode and QR code modes

Customization Options​

Label Printer Sizes​

You can customize label dimensions by calling the setLabelPrinterSize() function:

// Common label sizes in inches:
setLabelPrinterSize(1.125, 0.5); // 28.6mm x 12.7mm (address labels)
setLabelPrinterSize(1.1811, 0.5906); // 30mm x 15mm (default)
setLabelPrinterSize(2.125, 0.6875); // 54mm x 17.5mm (larger product labels)
setLabelPrinterSize(0.75, 0.5); // 19mm x 12.7mm (small price tags)
setLabelPrinterSize(4, 6); // 101.6mm x 152.4mm (shipping labels)

Style Customization​

Modify enhanced-labels.css to match your brand colors and preferences. The CSS uses CSS custom properties for easy theming.

Troubleshooting​

Common Issues​

  1. Preview not loading: Check browser console for JavaScript errors
  2. Print quality issues: Ensure proper barcode dimensions in settings
  3. Image not showing: Verify image URL accessibility and CORS settings
  4. Currency format wrong: Check currency settings in business configuration

Browser Compatibility​

  • Chrome 80+ (recommended)
  • Firefox 75+
  • Safari 13+
  • Edge 80+

Conclusion​

The enhanced label printing system provides a significant upgrade to the standard Ultimate POS label functionality, offering better usability, print quality, and customization options. The modular approach ensures compatibility with existing installations while providing powerful new features for modern label printing needs.