Subversion Repositories SmartDukaan

Rev

Rev 36962 | Blame | Compare with Previous | Last modification | View Log | RSS feed

<section class="wrapper">
    <div class="row">
        <div class="col-lg-12">
            <h3 class="page-header"><i class="icon_calendar"></i> Scheduled Beats - Day View</h3>
            <ol class="breadcrumb">
                <li><i class="fa fa-home"></i><a href="${rc.contextPath}/dashboard">Home</a></li>
                <li><i class="icon_group"></i> Partner Acquisition</li>
                <li><i class="icon_calendar"></i> Beat Day View</li>
            </ol>
        </div>
    </div>

    <div class="row" style="margin-bottom:10px;">
        <div class="col-lg-12" style="display:flex; gap:10px; align-items:center; flex-wrap:wrap;">
            <label style="margin:0;">Category:</label>
            <select id="dayViewCategory" class="form-control input-sm" style="width:140px;">
                <option value="$categoryId">Sales</option>
            </select>
            <label style="margin:0;">Level:</label>
            <select id="dayViewLevel" class="form-control input-sm" style="width:140px;">
                <option value="">All Levels</option>
                #foreach($lvl in $escalationTypes)
                    <option value="$lvl">$lvl</option>
                #end
            </select>
            <label style="margin:0;">Date:</label>
            <input type="date" id="dayViewDate" class="form-control input-sm" style="width:160px;">
            <button class="btn btn-primary btn-sm" id="dayViewLoad">Load</button>
            <span id="dayViewSummary" style="color:#777; font-size:12px; margin-left:10px;"></span>
        </div>
    </div>

    <div class="row">
        <div class="col-lg-12">
            <div class="table-responsive">
                <table class="table table-bordered table-striped table-hover" id="dayViewTable">
                    <thead>
                    <tr>
                        <th>User</th>
                        <th>Date</th>
                        <th>Beat</th>
                        <th>Day</th>
                        <th>Partners</th>
                        <th>Leads</th>
                        <th>Total Visits</th>
                        <th>Action</th>
                    </tr>
                    </thead>
                    <tbody id="dayViewRows">
                    <tr>
                        <td colspan="8" style="text-align:center; color:#999;">Pick a date range and click Load.</td>
                    </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</section>


<script>
    // Edit-beat / assign-visit in the Beat Planner are L2-and-above only.
    var CAN_EDIT_BEAT = $canEditBeat;
    (function () {
        // Default: today only. Other dates → use the per-user calendar from the row's View button.
        var today = new Date();

        function fmt(d) {
            return d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2);
        }

        $('#dayViewDate').val(fmt(today));
        loadDayView();
    })();

    $(document).on('click', '#dayViewLoad', function () {
        loadDayView();
    });

    function loadDayView() {
        var d = $('#dayViewDate').val();
        if (!d) {
            alert('Pick a date');
            return;
        }
        $('#dayViewRows').html('<tr><td colspan="8" style="text-align:center;">Loading...</td></tr>');
        $('#dayViewSummary').text('');
        var params = {startDate: d, endDate: d};
        var cat = $('#dayViewCategory').val();
        var lvl = $('#dayViewLevel').val();
        if (cat) params.categoryId = cat;
        if (lvl) params.escalationType = lvl;
        $.get(context + '/beatPlan/scheduledList', params).done(function (r) {
            var data = r.response || r;
            renderDayViewRows(data.rows || []);
            $('#dayViewSummary').text((data.rows || []).length + ' beats scheduled on ' + data.startDate);
        }).fail(function (xhr) {
            $('#dayViewRows').html('<tr><td colspan="8" style="color:#d9534f; text-align:center;">Error: ' + (xhr.responseText || xhr.statusText) + '</td></tr>');
        });
    }

    function renderDayViewRows(rows) {
        if (!rows || rows.length === 0) {
            $('#dayViewRows').html('<tr><td colspan="8" style="text-align:center; color:#999;">No beats scheduled in this range.</td></tr>');
            return;
        }
        // Assign Visit is only valid for TODAY's run — future/past dates hide the button.
        var todayStr = (function () {
            var d = new Date();
            var mm = ('0' + (d.getMonth() + 1)).slice(-2);
            var dd = ('0' + d.getDate()).slice(-2);
            return d.getFullYear() + '-' + mm + '-' + dd;
        })();
        var html = '';
        rows.forEach(function (r) {
            var isToday = r.scheduleDate === todayStr;
            html += '<tr>'
                    + '<td>' + r.userName + '</td>'
                    + '<td><span class="label label-primary">' + r.scheduleDate + '</span></td>'
                    + '<td><span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:' + (r.beatColor || '#3498db') + ';margin-right:6px;"></span>' + r.beatName + '</td>'
                    + '<td>' + r.dayNumber + '</td>'
                    + '<td>' + r.partnerCount + '</td>'
                    + '<td>' + (r.leadCount > 0 ? '<span class="label label-warning">' + r.leadCount + '</span>' : '0') + '</td>'
                    + '<td><strong>' + r.visitCount + '</strong></td>'
                    + '<td>'
                    + '<button class="btn btn-xs btn-info view-user-calendar" data-userid="' + r.authUserId + '" data-username="' + r.userName + '">View Calendar</button> '
                    + (CAN_EDIT_BEAT
                            ? '<button class="btn btn-xs btn-warning edit-beat-on-date" '
                            + 'data-userid="' + r.authUserId + '" data-username="' + r.userName + '" '
                            + 'data-beatid="' + r.beatId + '" data-date="' + r.scheduleDate + '">Edit</button>'
                            + (isToday
                                    ? ' <button class="btn btn-xs btn-success assign-visit-btn"'
                                    + ' data-userid="' + r.authUserId + '" data-username="' + r.userName + '"'
                                    + ' data-beatid="' + r.beatId + '"'
                                    + ' data-date="' + r.scheduleDate + '">Assign Visit</button>'
                                    : '')
                            // Add Lead is allowed for today AND future runs — a lead is a dated stop.
                            + ' <button class="btn btn-xs add-lead-btn"'
                            + ' style="background:#0d9488;color:#fff;border:none;"'
                            + ' data-userid="' + r.authUserId + '" data-username="' + r.userName + '"'
                            + ' data-beatid="' + r.beatId + '"'
                            + ' data-date="' + r.scheduleDate + '">Add Lead</button>'
                            : '')
                    + '</td>'
                    + '</tr>';
        });
        $('#dayViewRows').html(html);
    }

    // View -> open user's calendar inside the dashboard in a modal iframe (pre-selected)
    $(document).on('click', '.view-user-calendar', function () {
        var userId = $(this).data('userid');
        var userName = $(this).data('username') || '';
        var url = context + '/beatPlanWindow?autoUserId=' + userId
                + '&autoUserName=' + encodeURIComponent(userName);
        $('#userCalendarTitle').text('Calendar - ' + userName);
        $('#userCalendarFrame').attr('src', url);
        $('#userCalendarModal').modal('show');
    });

    // Edit a beat on a specific date — opens beatPlanWindow in edit mode for that run
    $(document).on('click', '.edit-beat-on-date', function () {
        var userId = $(this).data('userid');
        var userName = $(this).data('username') || '';
        var beatId = $(this).data('beatid');
        var date = $(this).data('date');
        var url = context + '/beatPlanWindow?autoUserId=' + userId
                + '&autoUserName=' + encodeURIComponent(userName)
                + '&editBeatId=' + beatId + '&editDate=' + date;
        $('#userCalendarTitle').text('Edit Beat - ' + userName + ' on ' + date);
        $('#userCalendarFrame').attr('src', url);
        $('#userCalendarModal').modal('show');
    });

    // ---------- ASSIGN VISIT ----------
    var avContext = {authUserId: null, userName: '', date: '', beatId: null, allParties: [], agendaOptions: []};

    $(document).on('click', '.assign-visit-btn', function () {
        avContext.authUserId = $(this).data('userid');
        avContext.userName = $(this).data('username') || '';
        avContext.date = $(this).data('date');
        avContext.beatId = $(this).data('beatid');
        avContext.allParties = [];

        $('#avTitle').text('Assign Visit - ' + avContext.userName + ' on ' + avContext.date);
        $('#avSearch').val('');
        // The default-agenda multiselect is rebuilt (and re-initialized) below in rebuildDefaultAgenda
        $('#avPartyList').html('<div style="padding:20px; color:#999; text-align:center;">Loading parties...</div>');
        $('#avMsg').text('');
        $('#avSelectedCount').text('0 selected');
        $('#avSubmit').prop('disabled', true);
        $('#assignVisitModal').modal('show');

        $.get(context + '/beatPlan/assignVisit/parties', {
            authUserId: avContext.authUserId,
            date: avContext.date,
            beatId: avContext.beatId
        }).done(function (r) {
            var data = r.response || r;
            // Drop parties already in the beat — they're handled by the beat plan itself,
            // Assign Visit is purely for adding extra (non-beat) visits.
            avContext.allParties = (data.parties || []).filter(function (p) {
                return !p.inBeat;
            });
            avContext.agendaOptions = data.agendaOptions || [];
            if (!data.dtrUserId) {
                $('#avPartyList').html('<div style="padding:20px; color:#c62828; text-align:center;">' +
                        'This user has no dtr.users record (cannot create visit tasks).</div>');
                return;
            }
            rebuildDefaultAgenda();
            renderAvParties(avContext.allParties);
        }).fail(function (xhr) {
            $('#avPartyList').html('<div style="padding:20px; color:#c62828; text-align:center;">' +
                    'Error loading parties: ' + (xhr.responseText || xhr.statusText) + '</div>');
        });
    });

    // Per-row agenda markup. Initialized as bootstrap-multiselect dropdown
    // (project standard — same as today-offer.vm, lead.vm, etc.) after render.
    // In-beat parties are filtered out of the list entirely, so only non-beat
    // (extra-visit) parties show up here. existingAgendas[] is pre-selected for
    // parties that already have a saved visit today (so editing existing ones works).
    function agendaSelectHtml(p) {
        var preset = {};
        (p.existingAgendas || []).forEach(function (a) {
            preset[a] = true;
        });
        var html = '<select multiple class="av-party-agenda"'
                + ' data-fofoid="' + p.fofoStoreId + '">';
        avContext.agendaOptions.forEach(function (opt) {
            html += '<option value="' + opt + '"' + (preset[opt] ? ' selected' : '') + '>' + opt + '</option>';
        });
        html += '</select>';
        return html;
    }

    var AGENDA_MULTISELECT_OPTS = {
        includeSelectAllOption: false,
        maxHeight: 220,
        buttonWidth: '240px',
        numberDisplayed: 2,
        nonSelectedText: 'Pick agenda(s)',
        nSelectedText: ' agendas',
        allSelectedText: 'All agendas',
        enableFiltering: true,
        enableCaseInsensitiveFiltering: true
    };

    function rebuildDefaultAgenda() {
        var $sel = $('#avDefaultAgenda');
        // Destroy any prior instance before re-initing (re-open of the modal)
        try {
                $sel.multiselect('destroy');
        } catch (e) {
        }
        var html = '';
        avContext.agendaOptions.forEach(function (opt) {
            html += '<option value="' + opt + '">' + opt + '</option>';
        });
            $sel.html(html);
        // Build opts as a variable — inline {...} object literal after $.extend()
        // confused Velocity's parser ("Encountered { ... was expecting ...").
        var defaultOpts = $.extend({}, AGENDA_MULTISELECT_OPTS);
        defaultOpts.nonSelectedText = 'Pick agenda(s) to apply';
            $sel.multiselect(defaultOpts);
    }

    function initRowMultiselects() {
        $('.av-party-agenda').each(function () {
            var $sel = $(this);
            try {
                    $sel.multiselect('destroy');
            } catch (e) {
            }
                $sel.multiselect(AGENDA_MULTISELECT_OPTS);
        });
    }

    function renderAvParties(list) {
        if (!list || list.length === 0) {
            $('#avPartyList').html('<div style="padding:20px; color:#999; text-align:center;">No parties found.</div>');
            return;
        }
        var html = '<table class="table table-condensed table-hover" style="margin-bottom:0; font-size:13px;">';
        html += '<thead><tr>'
                + '<th style="width:30px;"><input type="checkbox" id="avSelectAll" title="Select all"></th>'
                + '<th>Code</th><th>Outlet</th><th>City</th>'
                + '<th style="width:260px;">Agenda</th>'
                + '<th style="width:260px;">Description</th>'
                + '</tr></thead><tbody>';
        list.forEach(function (p) {
            var safeName = (p.outletName || '').replace(/"/g, '&quot;');
            var existingDesc = (p.existingDescription || '').replace(/"/g, '&quot;');
            html += '<tr>'
                    + '<td><input type="checkbox" class="av-party-chk" '
                    + 'data-fofoid="' + p.fofoStoreId + '" '
                    + 'data-name="' + safeName + '" '
                    + 'data-lat="' + (p.latitude || '') + '" '
                    + 'data-lng="' + (p.longitude || '') + '"></td>'
                    + '<td>' + (p.code || '') + '</td>'
                    + '<td>' + (p.outletName || '') + '</td>'
                    + '<td>' + (p.city || '') + '</td>'
                    + '<td>' + agendaSelectHtml(p) + '</td>'
                    + '<td><textarea class="form-control input-sm av-party-desc"'
                    + ' data-fofoid="' + p.fofoStoreId + '"'
                    + ' rows="2" placeholder="Optional notes..."'
                    + ' style="resize:vertical; min-height:34px;">' + existingDesc + '</textarea></td>'
                    + '</tr>';
        });
        html += '</tbody></table>';
        $('#avPartyList').html(html);
        initRowMultiselects();
        updateAvSelectedCount();
    }

    // Search filter
    $(document).on('input', '#avSearch', function () {
        var q = $(this).val().toLowerCase().trim();
        if (!q) {
            renderAvParties(avContext.allParties);
            return;
        }
        var filtered = avContext.allParties.filter(function (p) {
            return (p.code || '').toLowerCase().indexOf(q) !== -1
                    || (p.outletName || '').toLowerCase().indexOf(q) !== -1
                    || (p.city || '').toLowerCase().indexOf(q) !== -1;
        });
        renderAvParties(filtered);
    });

    // Select all toggle
    $(document).on('change', '#avSelectAll', function () {
        $('.av-party-chk').prop('checked', this.checked);
        updateAvSelectedCount();
    });

    $(document).on('change', '.av-party-chk', updateAvSelectedCount);

    function updateAvSelectedCount() {
        var n = $('.av-party-chk:checked').length;
        $('#avSelectedCount').text(n + ' selected');
        $('#avSubmit').prop('disabled', n === 0);
    }

    // Apply the default agendas (multi-select) to all currently-selected rows.
    // MERGES with each row's existing selections so in-beat rows keep prior agendas.
    // After updating <select>.val(), call multiselect('refresh') so the checkbox
    // UI re-syncs with the underlying option state.
    $(document).on('click', '#avApplyAgenda', function () {
        var defValues = $('#avDefaultAgenda').val() || [];
        if (defValues.length === 0) {
            alert('Pick at least one agenda above first');
            return;
        }
        $('.av-party-chk:checked').each(function () {
            var fid = $(this).data('fofoid');
            var $sel = $('.av-party-agenda[data-fofoid="' + fid + '"]');
            var existing = $sel.val() || [];
            var merged = existing.slice();
            defValues.forEach(function (v) {
                if (merged.indexOf(v) === -1) merged.push(v);
            });
                $sel.val(merged);
            try {
                    $sel.multiselect('refresh');
            } catch (e) {
            }
        });
    });

    // Submit
    $(document).on('click', '#avSubmit', function () {
        var picks = [];
        var missing = 0;
        $('.av-party-chk:checked').each(function () {
            var fid = parseInt($(this).data('fofoid'));
            var agendas = $('.av-party-agenda[data-fofoid="' + fid + '"]').val() || [];
            var description = ($('.av-party-desc[data-fofoid="' + fid + '"]').val() || '').trim();
            if (agendas.length === 0) missing++;
            picks.push({
                fofoStoreId: fid,
                outletName: $(this).data('name'),
                latitude: $(this).data('lat') ? String($(this).data('lat')) : null,
                longitude: $(this).data('lng') ? String($(this).data('lng')) : null,
                agendas: agendas,
                description: description
            });
        });
        if (picks.length === 0) return;
        if (missing > 0) {
            if (!confirm(missing + ' selected row(s) have no agenda. Submit anyway with default "Visit"?')) return;
        }

        var btn = $(this);
        btn.prop('disabled', true).text('Assigning...');
        $('#avMsg').text('');

        $.ajax({
            url: context + '/beatPlan/assignVisit/submit',
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({
                authUserId: avContext.authUserId,
                planDate: avContext.date,
                parties: picks
            }),
            success: function (r) {
                var d = r.response || r;
                $('#avMsg').html('<span style="color:#2e7d32;">' + (d.message || 'Saved') + '</span>');
                btn.prop('disabled', false).text('Save Assignments');
                setTimeout(function () {
                    $('#assignVisitModal').modal('hide');
                }, 1500);
            },
            error: function (xhr) {
                var msg = 'Failed';
                try {
                    msg = (JSON.parse(xhr.responseText).response.message) || msg;
                } catch (e) {
                }
                $('#avMsg').html('<span style="color:#c62828;">' + msg + '</span>');
                btn.prop('disabled', false).text('Save Assignments');
            }
        });
    });

    // ---------- ADD LEAD ----------
    // Routes the rep's assigned leads onto this scheduled beat/date. Writes an
    // APPROVED lead_route per pick via /beatPlan/leadsForBeat/submit.
    var alCtx = {authUserId: null, userName: '', beatId: null, date: '', allLeads: []};

    $(document).on('click', '.add-lead-btn', function () {
        alCtx.authUserId = $(this).data('userid');
        alCtx.userName = $(this).data('username') || '';
        alCtx.beatId = $(this).data('beatid');
        alCtx.date = $(this).data('date');
        alCtx.allLeads = [];

        $('#alTitle').text('Add Lead - ' + alCtx.userName + ' on ' + alCtx.date);
        $('#alSearch').val('');
        $('#alLeadList').html('<div style="padding:20px; color:#999; text-align:center;">Loading leads...</div>');
        $('#alMsg').text('');
        $('#alSelectedCount').text('0 selected');
        $('#alSubmit').prop('disabled', true);
        $('#addLeadModal').modal('show');

        $.get(context + '/beatPlan/assignedLeads', {
            authUserId: alCtx.authUserId,
            beatId: alCtx.beatId,
            date: alCtx.date
        }).done(function (r) {
            var data = r.response || r;
            alCtx.allLeads = data.rows || [];
            renderAlLeads(alCtx.allLeads);
        }).fail(function (xhr) {
            $('#alLeadList').html('<div style="padding:20px; color:#c62828; text-align:center;">' +
                    'Error loading leads: ' + (xhr.responseText || xhr.statusText) + '</div>');
        });
    });

    function renderAlLeads(list) {
        if (!list || list.length === 0) {
            $('#alLeadList').html('<div style="padding:20px; color:#999; text-align:center;">No active leads assigned to this user.</div>');
            return;
        }
        var html = '<table class="table table-condensed table-hover" style="margin-bottom:0; font-size:13px;">';
        html += '<thead><tr>'
                + '<th style="width:30px;"><input type="checkbox" id="alSelectAll" title="Select all"></th>'
                + '<th>Lead</th><th>Outlet</th><th>City</th><th>Stage</th><th style="width:80px;"></th>'
                + '</tr></thead><tbody>';
        list.forEach(function (p) {
            var onBeat = !!p.alreadyOnBeat;
            html += '<tr' + (onBeat ? ' style="opacity:.55;"' : '') + '>'
                    + '<td><input type="checkbox" class="al-lead-chk" data-lead="' + p.leadId + '"'
                    + (onBeat ? ' checked disabled' : '') + '></td>'
                    + '<td>' + (p.leadName || '') + (p.mobile ? ' <span style="color:#999;">· ' + p.mobile + '</span>' : '') + '</td>'
                    + '<td>' + (p.outletName || '') + '</td>'
                    + '<td>' + (p.city || '') + '</td>'
                    + '<td>' + (p.stage || '') + (p.hasGeo ? '' : ' <span class="label label-warning" title="No approved location">no geo</span>') + '</td>'
                    + '<td>' + (onBeat ? '<span class="label label-success">On beat</span>' : '') + '</td>'
                    + '</tr>';
        });
        html += '</tbody></table>';
        $('#alLeadList').html(html);
        updateAlSelectedCount();
    }

    $(document).on('input', '#alSearch', function () {
        var q = $(this).val().toLowerCase().trim();
        if (!q) {
            renderAlLeads(alCtx.allLeads);
            return;
        }
        var filtered = alCtx.allLeads.filter(function (p) {
            return (p.leadName || '').toLowerCase().indexOf(q) !== -1
                    || (p.outletName || '').toLowerCase().indexOf(q) !== -1
                    || (p.city || '').toLowerCase().indexOf(q) !== -1
                    || (p.mobile || '').toLowerCase().indexOf(q) !== -1;
        });
        renderAlLeads(filtered);
    });

    $(document).on('change', '#alSelectAll', function () {
        $('.al-lead-chk:not(:disabled)').prop('checked', this.checked);
        updateAlSelectedCount();
    });

    $(document).on('change', '.al-lead-chk', updateAlSelectedCount);

    function updateAlSelectedCount() {
        var n = $('.al-lead-chk:checked:not(:disabled)').length;
        $('#alSelectedCount').text(n + ' selected');
        $('#alSubmit').prop('disabled', n === 0);
    }

    $(document).on('click', '#alSubmit', function () {
        var ids = [];
        $('.al-lead-chk:checked:not(:disabled)').each(function () {
            ids.push(parseInt($(this).data('lead')));
        });
        if (ids.length === 0) return;
        var btn = $(this);
        btn.prop('disabled', true).text('Adding...');
        $('#alMsg').text('');
        $.ajax({
            url: context + '/beatPlan/leadsForBeat/submit',
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({
                authUserId: alCtx.authUserId,
                beatId: alCtx.beatId,
                date: alCtx.date,
                leadIds: ids
            }),
            success: function (r) {
                var d = r.response || r;
                $('#alMsg').html('<span style="color:#2e7d32;">' + (d.message || 'Saved') + '</span>');
                btn.prop('disabled', false).text('Add Leads');
                setTimeout(function () {
                    $('#addLeadModal').modal('hide');
                    loadDayView();
                }, 1200);
            },
            error: function (xhr) {
                var msg = 'Failed';
                try {
                    msg = (JSON.parse(xhr.responseText).response.message) || msg;
                } catch (e) {
                }
                $('#alMsg').html('<span style="color:#c62828;">' + msg + '</span>');
                btn.prop('disabled', false).text('Add Leads');
            }
        });
    });
</script>

<!-- Add Lead Modal -->
<div class="modal fade" id="addLeadModal" tabindex="-1" style="overflow-y:auto;">
    <div class="modal-dialog" style="width:90%; max-width:820px; margin-top:30px;">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">&times;</button>
                <h4 class="modal-title" id="alTitle">Add Lead</h4>
            </div>
            <div class="modal-body">
                <div style="display:flex; gap:10px; align-items:center; margin-bottom:10px;">
                    <input type="text" id="alSearch" class="form-control input-sm"
                           placeholder="Search by name, outlet, city or mobile..." style="flex:1;">
                    <span id="alSelectedCount" style="font-size:12px; color:#666;">0 selected</span>
                </div>
                <div id="alLeadList"
                     style="max-height:50vh; overflow-y:auto; border:1px solid #e5e5e5; border-radius:4px;"></div>
                <div id="alMsg" style="margin-top:10px; font-size:12px;"></div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
                <button type="button" class="btn btn-success" id="alSubmit" disabled>Add Leads</button>
            </div>
        </div>
    </div>
</div>

<!-- Assign Visit Modal -->
<div class="modal fade" id="assignVisitModal" tabindex="-1" style="overflow-y:auto;">
    <div class="modal-dialog" style="width:90%; max-width:900px; margin-top:30px;">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">&times;</button>
                <h4 class="modal-title" id="avTitle">Assign Visit</h4>
            </div>
            <div class="modal-body">
                <div style="display:flex; gap:10px; align-items:flex-start; margin-bottom:10px;">
                    <div style="flex:1;">
                        <label style="font-size:11px; color:#666; font-weight:normal; margin-bottom:2px;">
                            Agenda - applies to selected rows
                        </label>
                        <select id="avDefaultAgenda" multiple></select>
                    </div>
                    <button type="button" class="btn btn-default btn-sm" id="avApplyAgenda"
                            style="margin-top:18px;"
                            title="Add the picked agendas into all selected rows (merges with existing)">
                        Apply to Selected
                    </button>
                </div>
                <div style="display:flex; gap:10px; align-items:center; margin-bottom:10px;">
                    <input type="text" id="avSearch" class="form-control input-sm"
                           placeholder="Search by code, outlet name or city..." style="flex:1;">
                    <span id="avSelectedCount" style="font-size:12px; color:#666;">0 selected</span>
                </div>
                <div id="avPartyList"
                     style="max-height:50vh; overflow-y:auto; border:1px solid #e5e5e5; border-radius:4px;"></div>
                <div id="avMsg" style="margin-top:10px; font-size:12px;"></div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
                <button type="button" class="btn btn-success" id="avSubmit" disabled>Save Assignments</button>
            </div>
        </div>
    </div>
</div>

<!-- Calendar modal (iframe loads /beatPlanWindow with auto-selected user) -->
<div class="modal fade" id="userCalendarModal" tabindex="-1" style="overflow-y:auto;">
    <div class="modal-dialog" style="width:95%; max-width:1300px; margin-top:20px;">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal"
                        onclick="$('#userCalendarFrame').attr('src','');">&times;
                </button>
                <h4 class="modal-title" id="userCalendarTitle">Calendar</h4>
            </div>
            <div class="modal-body" style="padding:0;">
                <iframe id="userCalendarFrame" style="width:100%; height:80vh; border:none;"></iframe>
            </div>
        </div>
    </div>
</div>