# How to Add a Modal Button to Display Customer Images and Reasons in Laravel Bootstrap Tables

* In this tutorial, we'll implement a feature that allows administrators to view customer-uploaded images and reasons for return requests through a beautiful modal popup. This is perfect for e-commerce applications, support systems, or any application where customers submit requests with supporting images.
    
    ## 🎯 What We'll Build
    
    * A third action button in a Bootstrap table
        
    * A smooth modal popup displaying customer images in a gallery format
        
    * Customer reasons displayed in a styled card
        
    * Clickable images that open in full size
        
    * Responsive design that works on all devices
        
    * Proper error handling and loading states
        
    
    ## 📋 Prerequisites
    
    * Laravel 8+ application
        
    * Bootstrap Table implementation
        
    * SweetAlert2 for modals
        
    * Basic knowledge of PHP, JavaScript, and Laravel
        
    
    ## 🏗️ Project Structure
    
    We'll work with these key files:
    
    * `BootstrapTableService.php` - For generating action buttons
        
    * `OrderReturnRequestController.php` - Backend logic
        
    * `custom.js` - Frontend JavaScript
        
    * `list.blade.php` - View file with styling
        
    * `routes/web.php` - API routes
        
    
    ## Step 1: Database Model Setup
    
    First, ensure your model has the necessary fields and accessors:
    
    ```php
    <?php
    // app/Models/OrderReturnRequest.php
    
    namespace App\\\\Models;
    
    use Illuminate\\\\Database\\\\Eloquent\\\\Model;
    use Illuminate\\\\Support\\\\Facades\\\\Storage;
    
    class OrderReturnRequest extends Model
    {
        protected $fillable = [
            'order_id',
            'order_item_id',
            'type',
            'reason',
            'customer_image',  // JSON array of image paths
            'admin_image',
            'status',
            'requested_at',
            'status_at',
            'admin_receipt',
            'admin_notes'
        ];
    
        protected $casts = [
            'customer_image' => 'array',  // Important: Cast to array
            'admin_image' => 'array',
            'requested_at' => 'datetime',
            'status_at' => 'datetime',
        ];
    
        // Accessor to return full URLs for customer images
        public function getCustomerImageAttribute($value)
        {
            if ($value) {
                $images = json_decode($value, true);
                if (is_array($images)) {
                    return array_map(function ($path) {
                        return url(Storage::url($path));
                    }, $images);
                }
            }
            return [];
        }
    
        public function order()
        {
            return $this->belongsTo(Order::class);
        }
    
        public function orderItem()
        {
            return $this->belongsTo(OrderItem::class);
        }
    }
    
    ```
    
    ## Step 2: Create the Button Service Method
    
    Add a new method to your `BootstrapTableService` to generate the view button:
    
    ```php
    <?php
    // app/Services/BootstrapTableService.php
    
    public static function orderReturnRequestViewButton($orderReturnRequestId, $permission = null)
    {
        $customClass = [
            "OrderReturnRequestViewDetails",
            "btn-outline-success",
            "btn-xs",
            "btn-rounded",
            "btn-icon"
        ];
    
        $customAttributes = [
            "title" => trans("View Images & Reason"),
            "data-id" => $orderReturnRequestId
        ];
    
        $iconClass = "fa fa-image";  // FontAwesome image icon
        return self::button($iconClass, "#", $customClass, $customAttributes, $permission);
    }
    
    ```
    
    **Key Points:**
    
    * `OrderReturnRequestViewDetails` class for JavaScript event handling
        
    * `btn-outline-success` for green styling
        
    * `fa fa-image` icon to indicate image viewing functionality
        
    * `data-id` attribute to pass the request ID to JavaScript
        
    
    ## Step 3: Update the Controller
    
    Modify your controller to include the new button and create an API endpoint:
    
    ```php
    <?php
    // app/Http/Controllers/OrderReturnRequestController.php
    
    public function show()
    {
        try {
            // ... existing code for pagination and filtering ...
    
            $bulkData = [
                'total' => $total,
                'rows' => $rows->map(function ($row) {
                    // Generate action buttons
                    $operate = BootstrapTableService::orderReturnRequestStatusButton(
                        $row->id,
                        $row->status,
                        $row->admin_receipt,
                        $row->admin_image
                    );
    
                    // Add our new view button
                    $operate .= BootstrapTableService::orderReturnRequestViewButton($row->id);
    
                    // Add details button
                    $operate .= BootstrapTableService::detailsButton(
                        $row->order_id,
                        'order/details',
                        'order-view'
                    );
    
                    return [
                        'id' => $row->id,
                        'order_id' => $row->order_id,
                        'reason' => $row->reason,
                        'status' => ucfirst($row->status),
                        'status_badge' => $row->status,
                        'customer_image' => $row->customer_image, // Uses accessor
                        'operate' => $operate,
                        // ... other fields ...
                    ];
                })
            ];
    
            return response()->json($bulkData);
        } catch (\\\\Throwable $th) {
            $this->logError($th, 'show');
            return response()->json([
                'error' => true,
                'message' => trans('anErrorOccurred')
            ]);
        }
    }
    
    // New API endpoint to fetch return request details
    public function getReturnRequestDetails($id)
    {
        try {
            $returnRequest = OrderReturnRequest::findOrFail($id);
    
            return response()->json([
                'success' => true,
                'data' => [
                    'id' => $returnRequest->id,
                    'order_id' => $returnRequest->order_id,
                    'reason' => $returnRequest->reason,
                    'customer_image' => $returnRequest->customer_image, // Uses accessor
                    'status' => $returnRequest->status,
                    'requested_at' => $returnRequest->requested_at?->format('Y-m-d H:i:s'),
                ]
            ]);
        } catch (\\\\Throwable $th) {
            $this->logError($th, 'getReturnRequestDetails');
            return response()->json([
                'success' => false,
                'message' => trans('anErrorOccurred')
            ]);
        }
    }
    
    ```
    
    ## Step 4: Add the Route
    
    Add the new API route to your `routes/web.php`:
    
    ```php
    <?php
    // routes/web.php
    
    Route::group(['middleware' => ['auth']], function () {
        // ... existing routes ...
    
        Route::get('order-return-requests', [OrderReturnRequestController::class, 'index'])
            ->name('order-return-requests');
        Route::get('order-return-request-list', [OrderReturnRequestController::class, 'show'])
            ->name('order-return-request-list');
        Route::post('change-order-return-request-status', [OrderReturnRequestController::class, 'order_return_request_status_change'])
            ->name('change-order-return-request-status');
    
        // Our new route
        Route::get('get-return-request-details/{id}', [OrderReturnRequestController::class, 'getReturnRequestDetails'])
            ->name('get-return-request-details');
    });
    
    ```
    
    ## Step 5: JavaScript Implementation
    
    Add the JavaScript event handler to your `custom.js` file:
    
    ```jsx
    // public/assets/js/custom/custom.js
    
    // Handle View Images & Reason button click for Order Return Requests
    $(document).on("click", ".OrderReturnRequestViewDetails", function () {
        var request_id = $(this).attr("data-id");
    
        // Fetch return request details via AJAX
        $.ajax({
            url: baseUrl + "/get-return-request-details/" + request_id,
            type: "GET",
            dataType: "json",
            success: function (response) {
                if (response.success) {
                    var data = response.data;
                    var imagesHtml = '';
    
                    // Build images gallery
                    if (data.customer_image && data.customer_image.length > 0) {
                        imagesHtml = '<div class="images-gallery" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin: 15px 0;">';
    
                        data.customer_image.forEach(function (imageUrl, index) {
                            imagesHtml += `
                                <div class="image-item" style="text-align: center;">
                                    <img src="${imageUrl}"
                                         alt="Customer Image ${index + 1}"
                                         style="width: 100%; height: 120px; object-fit: cover; border-radius: 8px; cursor: pointer; border: 2px solid #ddd;"
                                         onclick="window.open('${imageUrl}', '_blank')">
                                    <small style="display: block; margin-top: 5px; color: #666;">
                                        Image ${index + 1}
                                    </small>
                                </div>`;
                        });
                        imagesHtml += '</div>';
                    } else {
                        imagesHtml = '<p style="text-align: center; color: #999; font-style: italic; margin: 15px 0;">No images uploaded by customer</p>';
                    }
    
                    // Show the modal with details
                    Swal.fire({
                        title: 'Return Request Details',
                        html: `
                            <div style="text-align: left; max-width: 600px; margin: 0 auto;">
                                <div style="margin-bottom: 20px;">
                                    <h4 style="color: #333; margin-bottom: 10px; border-bottom: 2px solid #007bff; padding-bottom: 5px;">
                                        <i class="fa fa-comment-o" style="margin-right: 8px;"></i>Customer Reason
                                    </h4>
                                    <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border-left: 4px solid #007bff;">
                                        <p style="margin: 0; line-height: 1.5; color: #555;">
                                            ${data.reason || 'No reason provided'}
                                        </p>
                                    </div>
                                </div>
    
                                <div>
                                    <h4 style="color: #333; margin-bottom: 15px; border-bottom: 2px solid #28a745; padding-bottom: 5px;">
                                        <i class="fa fa-image" style="margin-right: 8px;"></i>Customer Images
                                    </h4>
                                    ${imagesHtml}
                                </div>
                            </div>
                        `,
                        width: '700px',
                        showConfirmButton: true,
                        confirmButtonText: 'Close',
                        confirmButtonColor: '#007bff',
                        customClass: {
                            popup: 'return-request-details-modal'
                        }
                    });
                } else {
                    Swal.fire({
                        icon: 'error',
                        title: 'Error',
                        text: response.message || 'Failed to fetch return request details'
                    });
                }
            },
            error: function (xhr) {
                Swal.fire({
                    icon: 'error',
                    title: 'Error',
                    text: xhr.responseJSON?.message || 'An error occurred while fetching details'
                });
            }
        });
    });
    
    ```
    
    **Key JavaScript Features:**
    
    * **Event Delegation**: Uses `$(document).on()` for dynamically loaded content
        
    * **AJAX Request**: Fetches data without page reload
        
    * **Dynamic HTML Generation**: Builds image gallery dynamically
        
    * **Error Handling**: Proper error messages for failed requests
        
    * **Image Gallery**: CSS Grid layout for responsive images
        
    * **Click to View**: Images open in new tab when clicked
        
    
    ## Step 6: Add CSS Styling
    
    Add these styles to your Blade view file:
    
    ```php
    {{-- resources/views/order_return_request/list.blade.php --}}
    
    @section('style')
        <link href="<https://cdnjs.cloudflare.com/ajax/libs/ekko-lightbox/5.3.0/ekko-lightbox.css>" rel="stylesheet">
        <style>
            /* Return Request Details Modal Styling */
            .return-request-details-modal .images-gallery .image-item img:hover {
                border-color: #007bff !important;
                transform: scale(1.05);
                transition: all 0.3s ease;
            }
    
            .return-request-details-modal .images-gallery .image-item {
                transition: transform 0.2s ease;
            }
    
            .return-request-details-modal .images-gallery .image-item:hover {
                transform: translateY(-2px);
            }
    
            /* Custom scrollbar for modal content */
            .return-request-details-modal .swal2-popup {
                max-height: 90vh;
                overflow-y: auto;
            }
    
            /* Responsive grid for images */
            @media (max-width: 768px) {
                .return-request-details-modal .images-gallery {
                    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)) !important;
                }
            }
        </style>
    @endsection
    
    ```
    
    ## Step 7: HTML Table Structure
    
    Your Bootstrap table should include the actions column:
    
    ```php
    {{-- resources/views/order_return_request/list.blade.php --}}
    
    <table aria-describedby="mydesc" class='table' id='table_list'
           data-toggle="table"
           data-url="{{ route('order-return-request-list') }}"
           data-click-to-select="true"
           data-side-pagination="server"
           data-pagination="true"
           data-page-list="[5, 10, 20, 50, 100, 200,All]"
           data-search="true"
           data-show-columns="true"
           data-show-refresh="true"
           data-sort-name="id"
           data-sort-order="desc"
           data-query-params="OrderReturnRequestQueryParams">
        <thead>
            <tr>
                <th scope="col" data-field="id" data-sortable="true" data-visible="false">
                    {{ __('id') }}
                </th>
                <th scope="col" data-field="order_id" data-sortable="false">
                    {{ __('order_id') }}
                </th>
                <th scope="col" data-field="reason" data-sortable="false">
                    {{ __('reason') }}
                </th>
                <th scope="col" data-field="status_badge" data-sortable="false"
                    data-formatter="orderReturnRequestStatusFormatter" data-escape="false">
                    {{ __('status') }}
                </th>
                <th data-escape="false" scope="col" data-field="operate" data-sortable="false">
                    {{ __('actions') }}
                </th>
            </tr>
        </thead>
    </table>
    
    ```
    
    ## 🎨 UI/UX Features
    
    ### Responsive Image Gallery
    
    * **CSS Grid**: Automatically adjusts columns based on screen size
        
    * **Mobile Optimization**: Smaller images on mobile devices
        
    * **Hover Effects**: Smooth animations when hovering over images
        
    
    ### Professional Modal Design
    
    * **Structured Layout**: Clear sections for reason and images
        
    * **Color Coding**: Blue for reason, green for images
        
    * **Typography**: Proper spacing and font sizes
        
    * **Icons**: FontAwesome icons for visual clarity
        
    
    ### User Experience
    
    * **Smooth Animations**: No harsh popup transitions
        
    * **Loading States**: Button feedback during AJAX requests
        
    * **Error Handling**: User-friendly error messages
        
    * **Accessibility**: Proper alt texts and ARIA labels
        
    
    ## 🔧 Troubleshooting Common Issues
    
    ### 1\. Images Not Loading
    
    ```php
    // Check your storage configuration
    // config/filesystems.php
    'public' => [
        'driver' => 'local',
        'root' => storage_path('app/public'),
        'url' => env('APP_URL').'/storage',
        'visibility' => 'public',
    ],
    
    ```
    
    ### 2\. AJAX 404 Errors
    
    ```jsx
    // Make sure baseUrl is defined
    var baseUrl = "{{ url('/') }}";
    
    // Or use Laravel's route helper
    url: "{{ route('get-return-request-details', ':id') }}".replace(':id', request_id)
    
    ```
    
    ### 3\. Modal Not Appearing
    
    ```jsx
    // Ensure SweetAlert2 is loaded
    if (typeof Swal === 'undefined') {
        console.error('SweetAlert2 is not loaded');
    }
    
    ```
    
    ## 🚀 Advanced Enhancements
    
    ### 1\. Image Lightbox
    
    ```jsx
    // Add lightbox functionality
    imagesHtml += `<img src="${imageUrl}"
                   data-toggle="lightbox"
                   data-gallery="return-gallery">`;
    
    ```
    
    ### 2\. Image Zoom
    
    ```css
    .image-item img:hover {
        cursor: zoom-in;
    }
    
    ```
    
    ### 3\. Loading Spinner
    
    ```jsx
    beforeSend: function() {
        $('.OrderReturnRequestViewDetails[data-id="' + request_id + '"]')
            .html('<i class="fa fa-spinner fa-spin"></i>');
    }
    
    ```
    
    ## 📝 Best Practices
    
    1. **Security**: Always validate and sanitize user inputs
        
    2. **Performance**: Use pagination for large image sets
        
    3. **Accessibility**: Include proper alt texts and ARIA labels
        
    4. **Error Handling**: Provide meaningful error messages
        
    5. **Mobile First**: Design for mobile devices first
        
    6. **Caching**: Consider caching frequently accessed data
        
    
    ## 🎯 Conclusion
    
    You now have a complete implementation of a modal system for viewing customer images and reasons. This solution provides:
    
    * **Professional UI**: Clean, modern design that matches your application
        
    * **Responsive Design**: Works perfectly on all devices
        
    * **Smooth Animations**: No jarring transitions
        
    * **Error Handling**: Graceful error management
        
    * **Scalable Architecture**: Easy to extend and maintain
        
    
    The implementation follows Laravel best practices and provides a solid foundation for similar features in your application.
    
    ## 📚 Additional Resources
    
    * [Laravel Documentation](https://laravel.com/docs)
        
    * [SweetAlert2 Documentation](https://sweetalert2.github.io/)
        
    * [Bootstrap Table Documentation](https://bootstrap-table.com/)
        
    * [FontAwesome Icons](https://fontawesome.com/icons)
        
    
    Happy coding! 🚀
