{"id":66966,"date":"2024-04-03T10:59:53","date_gmt":"2024-04-03T14:59:53","guid":{"rendered":"https:\/\/termont2.com\/suivi-de-conteneur\/"},"modified":"2026-03-01T12:58:55","modified_gmt":"2026-03-01T11:58:55","slug":"suivi-de-conteneur","status":"publish","type":"page","link":"https:\/\/termont.com\/fr\/suivi-de-conteneur\/","title":{"rendered":"Suivi de conteneur"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"66966\" class=\"elementor elementor-66966\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-13976d8 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"13976d8\" data-element_type=\"section\" data-e-type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-no\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-278f92c\" data-id=\"278f92c\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-cb0f894 elementor-alert-info elementor-widget elementor-widget-alert\" data-id=\"cb0f894\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"alert.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-alert\" role=\"alert\">\n\n\t\t\t\t\t\t<span class=\"elementor-alert-title\">ENVIRONNEMENT D\u00c9MO<\/span>\n\t\t\t\n\t\t\t\t\t\t<span class=\"elementor-alert-description\">\u00c0 des fins de test, vous pouvez entrer n\u2019importe quel num\u00e9ro de r\u00e9servation et num\u00e9ro de conteneur, puis effectuer une recherche.<\/span>\n\t\t\t\n\t\t\t\t\t\t<button type=\"button\" class=\"elementor-alert-dismiss\" aria-label=\"Ignorer cette alerte.\">\n\t\t\t\t\t\t\t\t\t<span aria-hidden=\"true\">&times;<\/span>\n\t\t\t\t\t\t\t<\/button>\n\t\t\t\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\r\n\t\t<div class=\"elementor-element elementor-element-5d47b29 elementor-widget elementor-widget-shortcode\" data-id=\"5d47b29\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"shortcode.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-shortcode\">\n    <!-- Issue #6372: Hide legacy Elementor static placeholders -->\n    <style>\n        #section-dms-progressbar1-original-elementor-import,\n        #section-dms-progressbar2-original-elementor-import,\n        #section-dms-progressbar1-original-elementor-export,\n        #section-dms-progressbar2-original-elementor-export {\n            display: none !important;\n        }\n        [data-id=\"634ef27\"], [data-id=\"cb0f894\"] { display: none !important; }\n\n        \/* CT-specific: detail cards + form styles *\/\n        .form-control {\n            border: 1px solid grey;\n            box-shadow: 2px 2px 5px rgba(0,0,0,0.1);\n            transition: box-shadow 0.3s ease-in-out;\n        }\n        .form-control:focus {\n            box-shadow: 2px 2px 8px rgba(0,0,0,0.15);\n        }\n\n        #trackingResults { max-width: 960px; margin: 0 auto; }\n        #trackingStatusMessage { max-width: 960px; margin: 12px auto; }\n        \/* Issue #6381 M1+M3: Fix badge overlap + ensure proper Bootstrap rendering *\/\n        #trackingProgressBar { position: relative; z-index: 1; }\n        #trackingProgressBar .csb-special-states { margin-top: 8px; }\n        #trackingProgressBar .csb-special-states .badge {\n            display: inline-flex; align-items: center; gap: 4px;\n            font-size: 0.78rem; padding: 0.4em 0.7em;\n        }\n        #trackingProgressBar .csb-special-states .badge i { font-size: 0.72rem; }\n\n        @media (max-width: 768px) {\n            .tracking-detail-grid { grid-template-columns: repeat(3, 1fr); }\n            \/* Issue #6421 M8: Stack badges vertically on mobile *\/\n            .card-body .col-12 .badge { display: block; margin-bottom: 4px; width: fit-content; }\n            .card-body .col-12 { text-align: left !important; }\n        }\n    <\/style>\n\n    <!-- User guidance message -->\n    <div class=\"alert alert-info mx-auto mt-2 mb-3\" style=\"max-width:960px;\" role=\"alert\">\n        <i class=\"fas fa-info-circle me-2\"><\/i>\n                    Suivez le parcours de votre conteneur du navire &agrave; l'entrep&ocirc;t. Entrez un num&eacute;ro de conteneur (ex: MSDU5542806) ou un num&eacute;ro de r&eacute;servation, s&eacute;lectionnez Import ou Export, puis cliquez sur Rechercher.\n            <\/div>\n\n    <div class=\"container\">\n        <div class=\"row justify-content-center\">\n            <div class=\"col-md-10\">\n                <form id=\"trackingSearchForm\" method=\"post\" autocomplete=\"off\">\n                    <input type=\"hidden\" name=\"tracking_nonce\" value=\"c7e461342d\">\n                    <div class=\"row\">\n                        <div class=\"col-md-3\">\n                            <div class=\"mb-3\">\n                                <select class=\"form-control\" id=\"movement\" name=\"movement\" required>\n                                    <option value=\"\">Importation\/exportation ?<\/option>\n                                    <option value=\"import\" >Importation<\/option>\n                                    <option value=\"export\" >Exportation<\/option>\n                                <\/select>\n                            <\/div>\n                        <\/div>\n                        <div class=\"col-md-3\">\n                            <div class=\"form-group\">\n                                <span class=\"wpcf7-form-control-wrap\" data-name=\"booking\">\n                                    <input required size=\"40\"\n                                           class=\"wpcf7-form-control wpcf7-text wpcf7-validates-as-required form-control\"\n                                           id=\"booking\" placeholder=\"Booking or PIN\"\n                                           value=\"\"\n                                           type=\"text\" name=\"booking\">\n                                <\/span>\n                                <i class=\"fal fa-folder-open\"><\/i>\n                            <\/div>\n                            <div id=\"bookingError\" class=\"form-text text-danger\" style=\"display:none;\">Booking \/ PIN is required.<\/div>\n                        <\/div>\n                        <div class=\"col-md-3\">\n                            <div class=\"form-group\">\n                                <span class=\"wpcf7-form-control-wrap\" data-name=\"container\">\n                                    <input size=\"40\"\n                                           class=\"wpcf7-form-control wpcf7-text form-control\"\n                                           id=\"container\" placeholder=\"Container \/ Unit Inquiry\"\n                                           value=\"\"\n                                           type=\"text\" name=\"container\">\n                                <\/span>\n                                <i class=\"fal fa-box\"><\/i>\n                            <\/div>\n                        <\/div>\n                        <div class=\"col-md-3\">\n                            <div class=\"mb-3\">\n                                <button type=\"submit\" class=\"themeholy-btn th-btn wpcf7-old\" id=\"trackingSearchBtn\">\n                                    Search <span class=\"icon\"><i class=\"fal fa-sharp fa-regular fa-search\"><\/i><\/span>\n                                <\/button>\n                            <\/div>\n                        <\/div>\n                    <\/div>\n                <\/form>\n            <\/div>\n        <\/div>\n    <\/div>\n\n    <div id=\"trackingLoading\" class=\"text-center py-4\" style=\"display:none;\">\n        <div class=\"spinner-border text-primary\" role=\"status\">\n            <span class=\"visually-hidden\">Loading...<\/span>\n        <\/div>\n        <p class=\"text-muted mt-2\">Looking up container data...<\/p>\n    <\/div>\n\n    <div id=\"trackingError\" class=\"alert alert-danger mx-auto mt-3\" style=\"display:none; max-width:700px;\" role=\"alert\"><\/div>\n\n    <div id=\"trackingResults\" style=\"display:none;\">\n        <!-- Issue #6372: Progress bar rendered by ContainerStatusUI (Bootstrap-style, same as CI) -->\n        <div id=\"trackingProgressBar\" class=\"mb-3 mx-auto\" style=\"max-width:960px;\"><\/div>\n        <div id=\"trackingStatusMessage\"><\/div>\n        <div id=\"trackingDetailCards\" class=\"mb-3\"><\/div>\n        <div id=\"trackingEventsTable\" class=\"mb-4\"><\/div>\n    <\/div>\n\n    <script>\n    (function($) {\n        'use strict';\n\n        var ajaxUrl = 'https:\/\/termont.com\/wp-admin\/admin-ajax.php';\n        var nonce   = 'c7e461342d';\n        var isFr    = true;\n\n        \/\/ Issue #6372: Lazy lookup \u2014 modules load in footer, after this inline script\n        function CL() { return window.ContainerLifecycle; }\n        function UI() { return window.ContainerStatusUI; }\n\n        \/\/ Issue #6372 M7: Defensive getLabel wrapper \u2014 graceful fallback if lifecycle.js is old version\n        function getLabel(step, locale) {\n            var cl = CL();\n            if (cl && typeof cl.getLabel === 'function') return cl.getLabel(step, locale);\n            return step ? (step.name || '') : '';\n        }\n\n        \/\/ ===================================================================\n        \/\/ Form submit handler\n        \/\/ ===================================================================\n        $('#trackingSearchForm').on('submit', function(e) {\n            e.preventDefault();\n\n            var booking   = $('#booking').val().trim();\n            var container = $('#container').val().trim();\n            \/\/ Issue #6393 M4: Default to 'auto' when movement dropdown is empty (deep-link mode)\n            var movement  = $('#movement').val() || 'auto';\n\n            if (!booking && !container) {\n                $('#bookingError').show();\n                return;\n            }\n            $('#bookingError').hide();\n\n            $('#trackingResults').hide();\n            $('#trackingError').hide();\n            $('#trackingLoading').show();\n            $('#trackingSearchBtn').prop('disabled', true);\n\n            $.ajax({\n                url: ajaxUrl,\n                method: 'POST',\n                data: {\n                    action:    'container_tracking_lookup',\n                    nonce:     nonce,\n                    movement:  movement,\n                    booking:   booking,\n                    container: container\n                },\n                dataType: 'json',\n                timeout: 35000\n            })\n            .done(function(resp) {\n                $('#trackingLoading').hide();\n                $('#trackingSearchBtn').prop('disabled', false);\n\n                if (!resp.success) {\n                    showError(resp.data && resp.data.message ? resp.data.message : (isFr ? 'Conteneur non trouv\\u00e9.' : 'Container not found.'));\n                    return;\n                }\n\n                var apiData = resp.data.container_data;\n                if (apiData && apiData.data && typeof apiData.data === 'object' && apiData.data.container_info) {\n                    apiData = apiData.data;\n                }\n\n                \/\/ Issue #6393 M5: If movement was 'auto', update dropdown from API response\n                if (movement === 'auto' && resp.data && resp.data.movement) {\n                    movement = resp.data.movement;\n                    $('#movement').val(movement);\n                    console.log('[CT-AutoDetect] Movement resolved to:', movement);\n                }\n\n                renderResults(apiData, movement);\n            })\n            .fail(function(xhr, status, error) {\n                $('#trackingLoading').hide();\n                $('#trackingSearchBtn').prop('disabled', false);\n                showError((isFr ? 'Erreur: ' : 'Request failed: ') + (error || status));\n            });\n        });\n\n        function showError(msg) {\n            $('#trackingError').text(msg).show();\n            $('#trackingResults').hide();\n        }\n\n        \/\/ ===================================================================\n        \/\/ Issue #6372 M7: Render results using shared ContainerStatusUI\n        \/\/ Same DRY code path as Container Inquiry page\n        \/\/ ===================================================================\n        function renderResults(apiData, movement) {\n            var $results = $('#trackingResults');\n            var $bar     = $('#trackingProgressBar');\n            var $msg     = $('#trackingStatusMessage');\n            var $details = $('#trackingDetailCards');\n            var $events  = $('#trackingEventsTable');\n\n            $bar.empty(); $msg.empty(); $details.empty(); $events.empty();\n            $bar.prev('.alert-secondary').remove(); \/\/ Remove previous M17 type-mismatch notice\n\n            if (!apiData || (!apiData.container_info && !apiData.name && !apiData._operation)) {\n                showError(isFr ? 'Aucune donn\\u00e9e trouv\\u00e9e. V\\u00e9rifiez le num\\u00e9ro et r\\u00e9essayez.' : 'No container data found. Please verify the container number and try again.');\n                return;\n            }\n\n            \/\/ Use ContainerStatusEngine (same as CI)\n            var status = null;\n            if (window.ContainerStatusEngine) {\n                var tosData   = (apiData.storage_info && typeof apiData.storage_info === 'object')\n                              ? apiData.storage_info : {};\n                var tosSource = 'direct';\n                if (apiData._tos_empty_body) tosSource = 'empty';\n\n                status = window.ContainerStatusEngine.determineStatus(apiData, tosData, tosSource);\n            }\n\n            \/\/ Issue #6372 M7: Use ContainerStatusUI.renderStatusBar() \u2014 identical to CI\n            if (status && UI()) {\n                $bar.html(UI().renderStatusBar(status, 'frontend', 'default'));\n            }\n\n            \/\/ Issue #6421 M3: Storage tier color indicator below progress bar\n            var tos = (apiData && apiData.storage_info) || {};\n            var tosFree = parseInt(tos.nbr_days_free_storage) || 0;\n            var tosLowMax = parseInt(tos.max_nbr_days_low_rate) || 0;\n            var yardDateStr = (apiData && apiData.terminal_entry && apiData.terminal_entry.datetime) || (apiData && apiData.dates && apiData.dates.discharge_date) || '';\n            var daysSinceDischarge = yardDateStr ? Math.floor((new Date() - new Date(yardDateStr)) \/ 86400000) : 0;\n            if (daysSinceDischarge > 0 && (tosFree > 0 || tosLowMax > 0)) {\n                var maxD = Math.max(daysSinceDischarge + 10, tosFree + tosLowMax + 30);\n                var fPct = (tosFree \/ maxD * 100).toFixed(1);\n                var lPct = (tosLowMax \/ maxD * 100).toFixed(1);\n                var rPct = (100 - parseFloat(fPct) - parseFloat(lPct)).toFixed(1);\n                var tierHtml = '<div class=\"mx-auto mt-1 mb-2\" style=\"max-width:960px;\">';\n                tierHtml += '<div class=\"d-flex\" style=\"height:6px;border-radius:3px;overflow:hidden;\">';\n                tierHtml += '<div style=\"width:' + fPct + '%;background:#28a745;\" title=\"' + (isFr ? 'Gratuit: ' : 'Free: ') + tosFree + 'd\"><\/div>';\n                tierHtml += '<div style=\"width:' + lPct + '%;background:#ffc107;\" title=\"' + (isFr ? 'Tarif r\\u00e9duit: ' : 'Low rate: ') + tosLowMax + 'd\"><\/div>';\n                tierHtml += '<div style=\"width:' + rPct + '%;background:#dc3545;\" title=\"' + (isFr ? 'Tarif r\\u00e9gulier' : 'Regular rate') + '\"><\/div>';\n                tierHtml += '<\/div>';\n                tierHtml += '<small class=\"text-muted\">' + (isFr ? 'Gratuit' : 'Free') + '(' + tosFree + 'd) | ' + (isFr ? 'R\\u00e9duit' : 'Low') + '(' + tosLowMax + 'd) | ' + (isFr ? 'R\\u00e9gulier' : 'Regular') + ' &mdash; ' + (isFr ? 'Jour ' : 'Day ') + daysSinceDischarge + '<\/small>';\n                tierHtml += '<\/div>';\n                $bar.after(tierHtml);\n            }\n\n            \/\/ Issue #6421 M7: Fee projection (next 7 days)\n            var tosLowRate = parseFloat(tos.low_rate_one_teu_amount) || 0;\n            var tosRegRate = parseFloat(tos.regular_rate_one_teu_amount) || 0;\n            var currentRate = daysSinceDischarge > (tosFree + tosLowMax) ? tosRegRate : tosLowRate;\n            if (currentRate > 0 && daysSinceDischarge > tosFree) {\n                var weekProjection = (currentRate * 7).toFixed(2);\n                var projHtml = '<div class=\"mx-auto mb-2 text-center\" style=\"max-width:960px;\">';\n                projHtml += '<small class=\"text-muted\"><i class=\"fas fa-chart-line me-1\"><\/i>' + (isFr ? 'Projection: +$' : 'Projected: +$') + weekProjection + (isFr ? '\/semaine au tarif actuel ($' : '\/week at current rate ($') + currentRate.toFixed(2) + '\/TEU\/' + (isFr ? 'jour)' : 'day)') + '<\/small>';\n                projHtml += '<\/div>';\n                $bar.parent().find('.mx-auto.mt-1.mb-2').after(projHtml);\n            }\n\n            \/\/ Issue #6372: Engine type is authoritative, not user's ?m= param\n            var type = status ? status.type : movement;\n\n            \/\/ Issue #6338 M17: Show notice when engine detects different type than user selected\n            if (status && movement && movement !== 'auto' && type !== movement) {\n                var typeLabels = {import: 'Import', export: 'Export', empty: 'Empty Container', train: 'Rail Transport'};\n                var detected = typeLabels[type] || type;\n                var selected = typeLabels[movement] || movement;\n                $bar.before(\n                    '<div class=\"alert alert-secondary mx-auto mb-2\" style=\"max-width:960px;font-size:0.85rem;\" role=\"alert\">' +\n                    '<i class=\"fas fa-info-circle me-1\"><\/i> ' +\n                    (isFr ? 'D\\u00e9tect\\u00e9 comme: <strong>' + esc(detected) + '<\/strong> (s\\u00e9lectionn\\u00e9: ' + esc(selected) + ')'\n                         : 'Detected as: <strong>' + esc(detected) + '<\/strong> (selected: ' + esc(selected) + ')') +\n                    '<\/div>'\n                );\n            }\n\n            \/\/ Status message with bilingual step name\n            if (status && status.user_message) {\n                var msgColor = status.badge_color || 'info';\n                var steps = CL() ? CL().getSteps(type) : [];\n                var displayStepName = (status.step_number >= 1 && status.step_number <= steps.length)\n                    ? getLabel(steps[status.step_number - 1], isFr ? 'fr' : 'en')\n                    : status.step_name;\n                $msg.html(\n                    '<div class=\"alert alert-' + esc(msgColor) + ' mx-auto\" style=\"max-width:960px;\" role=\"alert\">' +\n                    status.icon + ' <strong>' + esc(displayStepName) + '<\/strong> &mdash; ' + esc(status.user_message) +\n                    '<\/div>'\n                );\n            }\n\n            \/\/ Detail cards (CT-specific: container info summary)\n            $details.html(buildDetailCardsHtml(apiData, status, type));\n\n            \/\/ Issue #6372: Events timeline \u2014 use shared UI renderer\n            \/\/ Issue #6417 M7: Inject berth\u2192yard gap as event if detected\n            if (status && status.events && status.events.length > 0 && UI()) {\n                var timelineEvents = [].concat(status.events);\n                if (typeof detectBerthYardGap === 'function') {\n                    var gapInfo = detectBerthYardGap(apiData);\n                    if (gapInfo && gapInfo.hasBerthGap && gapInfo.gapDays >= 2) {\n                        timelineEvents.push({\n                            date: gapInfo.berthDate,\n                            label: (isFr ? 'Navire \\u00e0 quai' : 'Vessel berthed'),\n                            icon: '\\u26f5',\n                            type: 'berth'\n                        });\n                        timelineEvents.push({\n                            date: gapInfo.yardDate,\n                            label: (isFr ? 'D\\u00e9charg\\u00e9 en cour' : 'Discharged to yard') + ' (' + gapInfo.gapDays + 'd gap)',\n                            icon: '\\ud83d\\udce6',\n                            type: 'discharge'\n                        });\n                        timelineEvents.sort(function(a, b) { return new Date(a.date) - new Date(b.date); });\n                    }\n                }\n                var eventsHtml = '<div class=\"mx-auto\" style=\"max-width:960px;\">';\n                eventsHtml += '<h5 class=\"mt-3\">' + (isFr ? '\\u00c9v\\u00e9nements sur ce conteneur' : 'Events on this container') + '<\/h5>';\n                eventsHtml += UI().renderEventsTimeline(timelineEvents);\n                eventsHtml += '<\/div>';\n                $events.html(eventsHtml);\n            }\n\n            \/\/ Special states\n            if (status && status.special_states && status.special_states.length > 0) {\n                var ssHtml = '<div class=\"mt-2 mx-auto\" style=\"max-width:960px;\">';\n                status.special_states.forEach(function(ss) {\n                    ssHtml += '<span class=\"badge bg-' + esc(ss.badge) + ' me-1\">' +\n                              ss.icon + ' ' + esc(ss.message) + '<\/span> ';\n                });\n                ssHtml += '<\/div>';\n                $events.append(ssHtml);\n            }\n\n            \/\/ Issue #6438: CN Railway Enrichment Card (CT page)\n            var cnData = apiData ? apiData.cn_railway : null;\n            if (cnData && cnData.shipment_id) {\n                var cnHtml = '<div class=\"card mt-3 mx-auto border-danger\" style=\"max-width:960px;border-left:4px solid #C21B17;\">';\n                cnHtml += '<div class=\"card-header bg-danger text-white py-2\">';\n                cnHtml += '<i class=\"fas fa-train me-2\"><\/i><strong>' + (isFr ? 'Suivi Intermodal CN' : 'CN Intermodal Tracking') + '<\/strong>';\n                cnHtml += '<span class=\"badge bg-light text-danger ms-2\">' + esc(cnData.waybill ? cnData.waybill.status_label || cnData.waybill.status : '') + '<\/span>';\n                cnHtml += '<\/div><div class=\"card-body py-2\">';\n\n                \/\/ Equipment row\n                cnHtml += '<div class=\"row mb-2\">';\n                cnHtml += '<div class=\"col-4\"><small class=\"text-muted\">' + (isFr ? '\\u00c9quipement' : 'Equipment') + '<\/small><br><strong>' + esc(cnData.equipment_id || '\\u2014') + '<\/strong><\/div>';\n                cnHtml += '<div class=\"col-4\"><small class=\"text-muted\">' + (isFr ? 'Type wagon' : 'Car Kind') + '<\/small><br><strong>' + esc(cnData.equipment ? cnData.equipment.car_kind || '\\u2014' : '\\u2014') + '<\/strong><\/div>';\n                cnHtml += '<div class=\"col-4\"><small class=\"text-muted\">' + (isFr ? 'Chargement' : 'Load') + '<\/small><br><strong>' + esc(cnData.equipment ? cnData.equipment.load_empty_status || '\\u2014' : '\\u2014') + '<\/strong><\/div>';\n                cnHtml += '<\/div>';\n\n                \/\/ Last event\n                if (cnData.last_event && cnData.last_event.description) {\n                    cnHtml += '<div class=\"alert alert-secondary py-1 px-2 mb-2\"><small>';\n                    cnHtml += '<i class=\"fas fa-clock me-1\"><\/i><strong>' + (isFr ? 'Dernier \\u00e9v\\u00e9nement' : 'Last Event') + ':<\/strong> ';\n                    cnHtml += esc(cnData.last_event.description) + ' \\u2014 ' + esc(cnData.last_event.city || '') + ' ' + esc(cnData.last_event.prov_state || '');\n                    if (cnData.last_event.time) cnHtml += '<br><span class=\"text-muted\">' + esc(cnData.last_event.time) + '<\/span>';\n                    cnHtml += '<\/small><\/div>';\n                }\n\n                \/\/ ETA + Destination + GPS\n                cnHtml += '<div class=\"row\">';\n                if (cnData.eta && cnData.eta.time) {\n                    cnHtml += '<div class=\"col-4\"><small class=\"text-muted\">ETA<\/small><br><strong class=\"text-success\">' + esc(cnData.eta.time) + '<\/strong><\/div>';\n                }\n                if (cnData.destination && cnData.destination.city) {\n                    cnHtml += '<div class=\"col-4\"><small class=\"text-muted\">Destination<\/small><br><strong>' + esc(cnData.destination.city) + ', ' + esc(cnData.destination.prov_state || '') + '<\/strong><\/div>';\n                }\n                if (cnData.gps && cnData.gps.latitude) {\n                    cnHtml += '<div class=\"col-4\"><small class=\"text-muted\">GPS<\/small><br><strong>' + parseFloat(cnData.gps.latitude).toFixed(4) + ', ' + parseFloat(cnData.gps.longitude).toFixed(4) + '<\/strong><\/div>';\n                }\n                cnHtml += '<\/div>';\n\n                cnHtml += '<\/div><\/div>'; \/\/ card-body + card\n                $events.append(cnHtml);\n            }\n\n            $results.show();\n        }\n\n        \/\/ ===================================================================\n        \/\/ Container info summary card (CT-specific)\n        \/\/ ===================================================================\n        function buildDetailCardsHtml(apiData, status, type) {\n            var ci    = (apiData && apiData.container_info) || {};\n            var vi    = (apiData && apiData.vessel_info) || {};\n            var dates = (apiData && apiData.dates) || {};\n            var op    = (apiData && apiData._operation) || {};\n            var td    = (op && op.terminalDetails) || {};\n\n            var containerNum = ci.name || ci.container_number || apiData.name || '';\n            var html = '';\n\n            if (containerNum) {\n                \/\/ Issue #6346 A1: Fix movement mapping \u2014 use engine status as authority, fallback chain\n                var category = (apiData._etl_container_cycle || {}).category || '';\n                var mov = (status ? status.movement : '') || ci.mov || category || '';\n                var movLabel = (mov === 'fullOut' || mov === 'fullout' || mov === 'import' || category === 'IMPRT') ? (isFr ? 'Importation' : 'Import')\n                             : (mov === 'fullIn' || mov === 'fullin' || mov === 'export' || category === 'EXPRT') ? (isFr ? 'Exportation' : 'Export')\n                             : (mov === 'emptyOut' || mov === 'emptyout') ? 'Empty Out'\n                             : (mov === 'emptyIn' || mov === 'emptyin') ? 'Empty In'\n                             : mov;\n                var vessel = op.vessel || td.vessel_name || vi.vessel_name || '';\n                var tcSize = ci.tc_size || ci.iso_code || '';\n                var shipowner = ci.shipowner || ci.shipping_line || vi.shipping_line || '';\n                var lfd = dates.last_free_day || '';\n\n                html += '<div class=\"card mt-3 mx-auto\" style=\"max-width:960px;\">';\n                html += '<div class=\"card-body py-2\">';\n                html += '<div class=\"row text-center\">';\n                html += infoCol(isFr ? 'Conteneur' : 'Container', containerNum);\n                html += infoCol(isFr ? 'Mouvement' : 'Movement', movLabel);\n                html += infoCol('Type', tcSize);\n                html += infoCol(isFr ? 'Navire' : 'Vessel', vessel);\n                html += infoCol(isFr ? 'Armateur' : 'Shipowner', shipowner);\n                html += infoCol(isFr ? 'Dernier jour libre' : 'Last Free Day', lfd);\n                \/\/ Issue #6346 A2: Hazmat badge on CT detail card\n                var isHazardous = ci.is_hazardous || (apiData.cargo_details || {}).is_hazardous || (status && status.is_hazardous);\n                if (isHazardous === true || isHazardous === 'true' || isHazardous === 1 || isHazardous === '1') {\n                    html += '<div class=\"col-12 mt-2\"><span class=\"badge bg-danger\"><i class=\"fas fa-radiation me-1\"><\/i>' + (isFr ? 'Mati\\u00e8res dangereuses' : 'Hazardous') + '<\/span><\/div>';\n                }\n\n                \/\/ Issue #6417 M8: Days-since-discharge live counter\n                var yardDate = (apiData.terminal_entry || {}).datetime || dates.discharge_date || '';\n                if (yardDate) {\n                    var daysSince = Math.floor((new Date() - new Date(yardDate)) \/ 86400000);\n                    if (daysSince > 0) {\n                        var daysColor = daysSince > 30 ? 'danger' : daysSince > 14 ? 'warning' : 'info';\n                        html += '<div class=\"col-12 mt-2\"><span class=\"badge bg-' + daysColor + '\"><i class=\"fas fa-clock me-1\"><\/i>' + daysSince + (isFr ? ' jours en entreposage' : ' days in storage') + '<\/span>';\n                    }\n                }\n\n                \/\/ Issue #6417 M10: LFD countdown \/ overdue badge\n                if (lfd) {\n                    var lfdDate = new Date(lfd);\n                    var daysUntilLfd = Math.floor((lfdDate - new Date()) \/ 86400000);\n                    if (daysUntilLfd < 0) {\n                        html += ' <span class=\"badge bg-danger\"><i class=\"fas fa-exclamation-triangle me-1\"><\/i>LFD: ' + Math.abs(daysUntilLfd) + (isFr ? 'j en retard' : 'd overdue') + '<\/span>';\n                    } else if (daysUntilLfd <= 3) {\n                        html += ' <span class=\"badge bg-warning text-dark\"><i class=\"fas fa-hourglass-half me-1\"><\/i>LFD: ' + daysUntilLfd + (isFr ? 'j restant' : 'd left') + '<\/span>';\n                    }\n                }\n\n                \/\/ Issue #6417 M6: Berth\u2192Yard gap badge on CT\n                if (typeof detectBerthYardGap === 'function') {\n                    var gapInfo = detectBerthYardGap(apiData);\n                    if (gapInfo && gapInfo.hasBerthGap && gapInfo.gapDays >= 2) {\n                        html += ' <span class=\"badge bg-' + (gapInfo.severity === 'high' ? 'warning text-dark' : 'info') + '\" title=\"' + (isFr ? 'Navire arriv\\u00e9 au quai le ' : 'Vessel berthed ') + gapInfo.berthDate + (isFr ? ', d\\u00e9charg\\u00e9 le ' : ', discharged to yard ') + gapInfo.yardDate + '\"><i class=\"fas fa-ship me-1\"><\/i>' + gapInfo.gapDays + 'd berth&rarr;yard<\/span>';\n                    }\n                }\n                \/\/ Issue #6421 M5: Gate-out prediction from appointment data\n                var apptDate = (op && op.appointment_date) || (op && op.date) || '';\n                var apptRef = (op && op.appointment_name) || (op && op.appointment_ref) || '';\n                if (apptDate && apptDate !== '0000-00-00') {\n                    html += ' <span class=\"badge bg-primary\"><i class=\"fas fa-calendar-check me-1\"><\/i>' + (isFr ? 'RDV: ' : 'Pickup: ') + esc(apptDate) + (apptRef ? ' (' + esc(apptRef) + ')' : '') + '<\/span>';\n                }\n\n                if (yardDate || lfd) html += '<\/div>';\n\n                \/\/ Issue #6421 M6: Container vs terminal average card\n                var avgDwell = (apiData._etl_container_cycle && apiData._etl_container_cycle.avg_dwell_time) ? parseInt(apiData._etl_container_cycle.avg_dwell_time) : 0;\n                var myDays = yardDate ? Math.floor((new Date() - new Date(yardDate)) \/ 86400000) : 0;\n                if (avgDwell > 0 && myDays > 0) {\n                    var ratio = (myDays \/ avgDwell).toFixed(1);\n                    var compColor = myDays > avgDwell * 2 ? 'danger' : myDays > avgDwell ? 'warning' : 'success';\n                    html += '<div class=\"mt-2 text-center\"><small class=\"text-muted\">';\n                    html += '<i class=\"fas fa-chart-bar me-1\"><\/i>';\n                    html += (isFr ? 'Votre conteneur: <strong>' : 'Your container: <strong>') + myDays + (isFr ? ' jours<\/strong> | Moyenne terminale: <strong>' : ' days<\/strong> | Terminal avg: <strong>') + avgDwell + (isFr ? ' jours<\/strong>' : ' days<\/strong>');\n                    html += ' <span class=\"badge bg-' + compColor + '\">' + ratio + 'x<\/span>';\n                    html += '<\/small><\/div>';\n                }\n\n                html += '<\/div><\/div><\/div>';\n            }\n\n            return html;\n        }\n\n        function infoCol(label, val) {\n            return '<div class=\"col-md-2 col-4 mb-1\"><small class=\"text-muted\">' + esc(label) + '<\/small><br><strong>' + esc(val || '\\u2014') + '<\/strong><\/div>';\n        }\n\n        function esc(str) {\n            if (!str) return '';\n            var div = document.createElement('div');\n            div.appendChild(document.createTextNode(String(str)));\n            return div.innerHTML;\n        }\n\n        \/\/ Auto-submit on page load when GET params are present\n        \n    })(jQuery);\n    <\/script>\n    <\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>ENVIRONNEMENT D\u00c9MO \u00c0 des fins de test, vous pouvez entrer n\u2019importe quel num\u00e9ro de r\u00e9servation et num\u00e9ro de conteneur, puis effectuer une recherche. &times;<\/p>\n","protected":false},"author":0,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-66966","page","type-page","status-publish","hentry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Suivi de conteneur - Termont Montreal Inc<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/termont.com\/fr\/suivi-de-conteneur\/\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Suivi de conteneur - Termont Montreal Inc\" \/>\n<meta property=\"og:description\" content=\"ENVIRONNEMENT D\u00c9MO \u00c0 des fins de test, vous pouvez entrer n\u2019importe quel num\u00e9ro de r\u00e9servation et num\u00e9ro de conteneur, puis effectuer une recherche. &times;\" \/>\n<meta property=\"og:url\" content=\"https:\/\/termont.com\/fr\/suivi-de-conteneur\/\" \/>\n<meta property=\"og:site_name\" content=\"Termont Montreal Inc\" \/>\n<meta property=\"article:modified_time\" content=\"2026-03-01T11:58:55+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/termont.com\\\/fr\\\/suivi-de-conteneur\\\/\",\"url\":\"https:\\\/\\\/termont.com\\\/fr\\\/suivi-de-conteneur\\\/\",\"name\":\"Suivi de conteneur - Termont Montreal Inc\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/termont.com\\\/fr\\\/#website\"},\"datePublished\":\"2024-04-03T14:59:53+00:00\",\"dateModified\":\"2026-03-01T11:58:55+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/termont.com\\\/fr\\\/suivi-de-conteneur\\\/#breadcrumb\"},\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/termont.com\\\/fr\\\/suivi-de-conteneur\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/termont.com\\\/fr\\\/suivi-de-conteneur\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/termont.com\\\/fr\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Suivi de conteneur\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/termont.com\\\/fr\\\/#website\",\"url\":\"https:\\\/\\\/termont.com\\\/fr\\\/\",\"name\":\"Termont Montreal Inc\",\"description\":\"Le monde passe par nous\",\"publisher\":{\"@id\":\"https:\\\/\\\/termont.com\\\/fr\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/termont.com\\\/fr\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"fr-FR\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/termont.com\\\/fr\\\/#organization\",\"name\":\"Termont Montreal Inc\",\"url\":\"https:\\\/\\\/termont.com\\\/fr\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\\\/\\\/termont.com\\\/fr\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/termont.com\\\/wp-content\\\/uploads\\\/2023\\\/07\\\/logo-termont.png\",\"contentUrl\":\"https:\\\/\\\/termont.com\\\/wp-content\\\/uploads\\\/2023\\\/07\\\/logo-termont.png\",\"width\":325,\"height\":59,\"caption\":\"Termont Montreal Inc\"},\"image\":{\"@id\":\"https:\\\/\\\/termont.com\\\/fr\\\/#\\\/schema\\\/logo\\\/image\\\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Suivi de conteneur - Termont Montreal Inc","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/termont.com\/fr\/suivi-de-conteneur\/","og_locale":"fr_FR","og_type":"article","og_title":"Suivi de conteneur - Termont Montreal Inc","og_description":"ENVIRONNEMENT D\u00c9MO \u00c0 des fins de test, vous pouvez entrer n\u2019importe quel num\u00e9ro de r\u00e9servation et num\u00e9ro de conteneur, puis effectuer une recherche. &times;","og_url":"https:\/\/termont.com\/fr\/suivi-de-conteneur\/","og_site_name":"Termont Montreal Inc","article_modified_time":"2026-03-01T11:58:55+00:00","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/termont.com\/fr\/suivi-de-conteneur\/","url":"https:\/\/termont.com\/fr\/suivi-de-conteneur\/","name":"Suivi de conteneur - Termont Montreal Inc","isPartOf":{"@id":"https:\/\/termont.com\/fr\/#website"},"datePublished":"2024-04-03T14:59:53+00:00","dateModified":"2026-03-01T11:58:55+00:00","breadcrumb":{"@id":"https:\/\/termont.com\/fr\/suivi-de-conteneur\/#breadcrumb"},"inLanguage":"fr-FR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/termont.com\/fr\/suivi-de-conteneur\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/termont.com\/fr\/suivi-de-conteneur\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/termont.com\/fr\/"},{"@type":"ListItem","position":2,"name":"Suivi de conteneur"}]},{"@type":"WebSite","@id":"https:\/\/termont.com\/fr\/#website","url":"https:\/\/termont.com\/fr\/","name":"Termont Montreal Inc","description":"Le monde passe par nous","publisher":{"@id":"https:\/\/termont.com\/fr\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/termont.com\/fr\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"fr-FR"},{"@type":"Organization","@id":"https:\/\/termont.com\/fr\/#organization","name":"Termont Montreal Inc","url":"https:\/\/termont.com\/fr\/","logo":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/termont.com\/fr\/#\/schema\/logo\/image\/","url":"https:\/\/termont.com\/wp-content\/uploads\/2023\/07\/logo-termont.png","contentUrl":"https:\/\/termont.com\/wp-content\/uploads\/2023\/07\/logo-termont.png","width":325,"height":59,"caption":"Termont Montreal Inc"},"image":{"@id":"https:\/\/termont.com\/fr\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/termont.com\/fr\/wp-json\/wp\/v2\/pages\/66966","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/termont.com\/fr\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/termont.com\/fr\/wp-json\/wp\/v2\/types\/page"}],"replies":[{"embeddable":true,"href":"https:\/\/termont.com\/fr\/wp-json\/wp\/v2\/comments?post=66966"}],"version-history":[{"count":3,"href":"https:\/\/termont.com\/fr\/wp-json\/wp\/v2\/pages\/66966\/revisions"}],"predecessor-version":[{"id":66976,"href":"https:\/\/termont.com\/fr\/wp-json\/wp\/v2\/pages\/66966\/revisions\/66976"}],"wp:attachment":[{"href":"https:\/\/termont.com\/fr\/wp-json\/wp\/v2\/media?parent=66966"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}