<?php $this->start('body'); ?>
<?php
    $contractId = (int)($contract['c0d'] ?? 0);
    $sitesTarget = (int)($contract['sites_target_confirmed'] ?? 0);
    // Equipment target — sum of counts from Slice 8Z-y equipment_scope_confirmed_json (aggregate).
    $equipJson   = (string)($contract['equipment_scope_confirmed_json'] ?? '');
    $equipTotal  = 0;
    if ($equipJson !== '') {
        $decoded = json_decode($equipJson, true);
        if (is_array($decoded)) foreach ($decoded as $r) $equipTotal += (int)($r['count'] ?? 0);
    }
?>

<!-- ── Slice 8Z-z-A · Project sites & POCs · Vue 3 Options API root ── -->
<style>[v-cloak]{display:none !important;}</style>

<div class="d-flex align-items-center mb-3">
    <a href="<?= PROOT; ?>Projects/view/<?= (int)$p['c0d']; ?>" class="btn btn-sm" style="background:transparent;border:1px solid #E2E8F0;color:#64748B;font-weight:500;">
        <i class="fas fa-arrow-left me-1"></i>Back to project
    </a>
    <div class="ms-3">
        <div class="small text-muted" style="color:#94A3B8;font-size:.66rem;letter-spacing:.4px;text-transform:uppercase;">Project sites</div>
        <div style="color:#334155;font-weight:700;font-size:1.02rem;">
            <?= htmlspecialchars((string)($p['project_no'] ?? '')); ?> ·
            <?= htmlspecialchars((string)($p['project_name'] ?? '')); ?>
        </div>
    </div>
</div>

<?php if ($contractId === 0): ?>
    <div class="alert alert-warning small">No service contract on file — the project must have a services contract before sites can be added.</div>
<?php else: ?>

<div id="sites_root" v-cloak>

    <!-- KPI strip -->
    <div class="row g-2 mb-3">
        <div class="col-md-3">
            <div class="card border-0 shadow-sm h-100" style="border-left:3px solid #7A9E7E !important;">
                <div class="card-body p-2">
                    <div class="small" style="color:#94A3B8;font-size:.62rem;letter-spacing:.4px;text-transform:uppercase;font-weight:600;">Sites</div>
                    <div style="color:#334155;font-weight:700;font-size:1.35rem;">{{ sites.length }}<span class="ms-1 small" style="color:#94A3B8;font-size:.72rem;font-weight:500;">of <?= $sitesTarget ?: '—'; ?></span></div>
                </div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="card border-0 shadow-sm h-100" style="border-left:3px solid #7A8BA1 !important;">
                <div class="card-body p-2">
                    <div class="small" style="color:#94A3B8;font-size:.62rem;letter-spacing:.4px;text-transform:uppercase;font-weight:600;">POCs confirmed</div>
                    <div style="color:#334155;font-weight:700;font-size:1.35rem;">{{ confirmedCount }}<span class="ms-1 small" style="color:#94A3B8;font-size:.72rem;font-weight:500;">of {{ sites.length }}</span></div>
                </div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="card border-0 shadow-sm h-100" style="border-left:3px solid #D4A574 !important;">
                <div class="card-body p-2">
                    <div class="small" style="color:#94A3B8;font-size:.62rem;letter-spacing:.4px;text-transform:uppercase;font-weight:600;">Equipment logged</div>
                    <div style="color:#334155;font-weight:700;font-size:1.35rem;">{{ assets.length }}<span class="ms-1 small" style="color:#94A3B8;font-size:.72rem;font-weight:500;">of <?= $equipTotal ?: '—'; ?></span></div>
                </div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="card border-0 shadow-sm h-100" style="border-left:3px solid #94A3B8 !important;">
                <div class="card-body p-2">
                    <div class="small" style="color:#94A3B8;font-size:.62rem;letter-spacing:.4px;text-transform:uppercase;font-weight:600;">Onboarding</div>
                    <div style="color:#334155;font-weight:700;font-size:1.35rem;">{{ progressPct }}%</div>
                </div>
            </div>
        </div>
    </div>

    <!-- Project-level POCs · 8Z-z-A' · inherited by every site by default -->
    <div class="card border-0 shadow-sm mb-3" style="border-left:3px solid #7A8BA1 !important;">
        <div class="card-header bg-white d-flex align-items-center justify-content-between" style="border-bottom:1px solid #E2E8F0;">
            <div>
                <strong style="color:#334155;"><i class="fas fa-address-book me-2" style="color:#7A8BA1;"></i>Project-level POCs</strong>
                <span class="text-muted ms-2" style="color:#94A3B8;font-size:.68rem;">Client-side contacts that apply to every site by default. Site-specific POCs override these inside a site drawer.</span>
            </div>
            <?php if ($can_edit): ?>
            <button type="button" @click="openProjectPocAdd" class="btn btn-sm" style="background:#7A8BA1;border-color:#7A8BA1;color:#fff;font-weight:600;">
                <i class="fas fa-plus me-1"></i>Add POC
            </button>
            <?php endif; ?>
        </div>
        <div class="card-body p-2">
            <div v-if="!projectPocs.length" class="p-2 text-center" style="color:#94A3B8;font-size:.74rem;">
                No project-level POCs yet. Sites will show <em>"no default"</em> until at least one is added.
            </div>
            <div v-else class="d-flex flex-wrap align-items-start gap-2 p-1">
                <div v-for="pc in projectPocs" :key="'ppc_'+pc.c0d"
                     class="p-2" style="background:#F8FAFC;border:1px solid #E2E8F0;border-radius:6px;min-width:220px;">
                    <div class="d-flex align-items-center gap-1 mb-1">
                        <span class="badge" :style="'background:'+projectRoleColor(pc.poc_role)+';color:#fff;font-size:.58rem;font-weight:600;padding:.15rem .4rem;text-transform:capitalize;'">
                            {{ projectRoleLabel(pc.poc_role) }}
                        </span>
                        <span v-if="pc.is_primary == 1" class="badge" style="background:#7A9E7E;color:#fff;font-size:.55rem;font-weight:600;padding:.1rem .3rem;">PRIMARY</span>
                    </div>
                    <div style="color:#334155;font-weight:600;font-size:.82rem;line-height:1.2;">{{ pc.poc_name }}</div>
                    <div v-if="pc.poc_phone" class="small" style="color:#64748B;font-size:.7rem;">
                        <a :href="'tel:' + pc.poc_phone" style="color:#64748B;text-decoration:none;"><i class="fas fa-phone me-1" style="font-size:.58rem;"></i>{{ pc.poc_phone }}</a>
                    </div>
                    <div v-if="pc.poc_email" class="small" style="color:#64748B;font-size:.7rem;">
                        <a :href="'mailto:' + pc.poc_email" style="color:#64748B;text-decoration:none;"><i class="fas fa-envelope me-1" style="font-size:.58rem;"></i>{{ pc.poc_email }}</a>
                    </div>
                    <?php if ($can_edit): ?>
                    <div class="mt-1">
                        <button type="button" @click="openProjectPocEdit(pc)" class="btn btn-sm" style="background:transparent;border:none;color:#7A8BA1;padding:0 .3rem 0 0;font-size:.62rem;"><i class="fas fa-pencil-alt me-1"></i>Edit</button>
                        <button type="button" @click="removeProjectPoc(pc)" class="btn btn-sm" style="background:transparent;border:none;color:#C48579;padding:0 .3rem;font-size:.62rem;"><i class="fas fa-times me-1"></i>Remove</button>
                    </div>
                    <?php endif; ?>
                </div>
            </div>
        </div>
    </div>

    <!-- Import panel · Vue3 Options API driven (no jquery-confirm modal) -->
    <div v-if="importOpen" class="card border-0 shadow-sm mb-3" style="border-left:3px solid #7A9E7E !important;">
        <div class="card-header bg-white d-flex align-items-center justify-content-between" style="border-bottom:1px solid #E2E8F0;">
            <div>
                <strong style="color:#334155;"><i class="fas fa-file-upload me-2" style="color:#7A9E7E;"></i>Import sites from Excel</strong>
                <div class="text-muted small mt-1" style="color:#94A3B8;font-size:.68rem;">
                    Rows matched by <code style="color:#7A8BA1;background:#EEF2F6;padding:0 .2rem;">site_code</code> are updated; new codes are inserted; primary POC name (if present) is upserted as a SITE_MANAGER row.
                </div>
            </div>
            <button type="button" @click="closeImport" class="btn btn-sm" style="background:transparent;border:none;color:#94A3B8;">
                <i class="fas fa-times"></i>
            </button>
        </div>
        <div class="card-body p-3">
            <div class="alert py-2 mb-3" style="background:#F8FAFC;border:1px solid #E2E8F0;color:#334155;font-size:.72rem;">
                <i class="fas fa-info-circle me-1" style="color:#7A9E7E;"></i>New to the format?
                <a href="#" @click.prevent="downloadTemplate" style="color:#7A9E7E;font-weight:600;text-decoration:none;">Download the sample template</a> — 5 example rows plus an Instructions sheet.
            </div>

            <div class="row g-2 align-items-end">
                <div class="col-md-8">
                    <label class="form-label small text-muted" style="font-size:.68rem;text-transform:uppercase;letter-spacing:.4px;font-weight:600;">Excel file</label>
                    <input type="file" ref="importFile" @change="onImportFileChange" class="form-control form-control-sm" accept=".xlsx,.xls,.csv">
                </div>
                <div class="col-md-4 text-md-end">
                    <span v-if="importFileName" class="small" style="color:#64748B;font-size:.72rem;">
                        <i class="fas fa-file-excel me-1" style="color:#7A9E7E;"></i>{{ importFileName }}
                        <span v-if="importRows.length" style="color:#94A3B8;">· {{ importRows.length }} row{{ importRows.length === 1 ? '' : 's' }}</span>
                    </span>
                </div>
            </div>

            <div v-if="importParseError" class="alert py-2 mt-3 mb-0" style="background:#FBEEEA;border:1px solid #C48579;color:#8A4A3A;font-size:.78rem;">
                <i class="fas fa-exclamation-triangle me-1"></i>{{ importParseError }}
            </div>

            <div v-if="importRows.length" class="mt-3">
                <div class="small mb-2" style="color:#334155;font-weight:600;font-size:.74rem;">
                    Preview
                    <span v-if="importRows.length > 10" style="color:#94A3B8;font-weight:400;">· showing first 10 of {{ importRows.length }}</span>
                </div>
                <div style="max-height:280px;overflow:auto;border:1px solid #E2E8F0;border-radius:4px;">
                    <table class="table table-sm mb-0" style="font-size:.7rem;">
                        <thead style="background:#F8FAFC;position:sticky;top:0;">
                            <tr style="color:#64748B;font-size:.62rem;text-transform:uppercase;letter-spacing:.3px;">
                                <th v-for="col in importPreviewCols" :key="'ic_'+col">{{ col }}</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr v-for="(r, i) in importRows.slice(0, 10)" :key="'ir_'+i">
                                <td v-for="col in importPreviewCols" :key="'irc_'+i+'_'+col" style="color:#334155;">{{ r[col] }}</td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>

            <div class="mt-3 d-flex align-items-center justify-content-end gap-2">
                <button type="button" @click="closeImport" class="btn btn-sm" style="background:transparent;border:1px solid #E2E8F0;color:#64748B;font-weight:500;">Cancel</button>
                <button type="button" @click="runImport" :disabled="!importRows.length || importBusy"
                        class="btn btn-sm" style="background:#7A9E7E;border-color:#7A9E7E;color:#fff;font-weight:600;">
                    <i class="fas" :class="importBusy ? 'fa-spinner fa-spin' : 'fa-check'"></i>
                    <span class="ms-1">{{ importBusy ? 'Importing…' : ('Import ' + importRows.length + ' row(s)') }}</span>
                </button>
            </div>
        </div>
    </div>

    <!-- Equipment blueprints · 8Z-z-B · per-contract templates -->
    <div class="card border-0 shadow-sm mb-3" style="border-left:3px solid #D4A574 !important;">
        <div class="card-header bg-white d-flex align-items-center justify-content-between" style="border-bottom:1px solid #E2E8F0;">
            <div>
                <strong style="color:#334155;"><i class="fas fa-clone me-2" style="color:#D4A574;"></i>Equipment blueprints</strong>
                <span class="text-muted ms-2" style="color:#94A3B8;font-size:.68rem;">Per-contract templates. Use for bulk-apply (N units × M sites) and as Excel-import shortcuts (<code style="color:#7A8BA1;background:#EEF2F6;padding:0 .2rem;">spec_code</code>).</span>
            </div>
            <?php if ($can_edit): ?>
            <div>
                <button type="button" @click="openAssetImport" class="btn btn-sm" style="background:transparent;border:1px solid #7A9E7E;color:#7A9E7E;font-weight:500;">
                    <i class="fas fa-file-upload me-1"></i>Import equipment
                </button>
                <button type="button" @click="openSpecAdd" class="btn btn-sm" style="background:#D4A574;border-color:#D4A574;color:#fff;font-weight:600;">
                    <i class="fas fa-plus me-1"></i>Add blueprint
                </button>
            </div>
            <?php endif; ?>
        </div>
        <div class="card-body p-2">
            <div v-if="!assetSpecs.length" class="p-2 text-center" style="color:#94A3B8;font-size:.74rem;">
                No blueprints yet. Add one to capture a standard config (e.g. "Cummins C40 40kVA · 12-mo warranty") and then bulk-apply it to sites.
            </div>
            <div v-else class="d-flex flex-wrap gap-2 p-1">
                <div v-for="sp in assetSpecs" :key="'sp_'+sp.c0d"
                     class="p-2" style="background:#F8FAFC;border:1px solid #E2E8F0;border-radius:6px;min-width:260px;flex:1 1 260px;max-width:340px;">
                    <div class="d-flex align-items-center gap-1 mb-1">
                        <i :class="'fas ' + (sp.asset_type_icon || 'fa-cube')" style="color:#D4A574;"></i>
                        <code style="color:#D4A574;font-size:.7rem;background:#FBF4EA;padding:.1rem .3rem;border-radius:2px;font-weight:600;">{{ sp.spec_code }}</code>
                        <span class="badge ms-auto" style="background:#EEF2F6;color:#64748B;font-size:.58rem;font-weight:500;padding:.15rem .35rem;">{{ sp.units_deployed }} unit{{ sp.units_deployed == 1 ? '' : 's' }} live</span>
                    </div>
                    <div style="color:#334155;font-weight:600;font-size:.82rem;line-height:1.2;">{{ sp.spec_name }}</div>
                    <div class="small" style="color:#64748B;font-size:.68rem;">{{ sp.asset_type_name }}<span v-if="sp.size_value"> · {{ sp.size_value }} {{ sp.size_unit }}</span></div>
                    <div v-if="sp.make || sp.model" class="small" style="color:#64748B;font-size:.68rem;">{{ [sp.make, sp.model].filter(Boolean).join(' ') }}</div>
                    <div v-if="sp.default_warranty_months" class="small" style="color:#94A3B8;font-size:.62rem;"><i class="fas fa-shield-alt me-1"></i>{{ sp.default_warranty_months }}-mo warranty</div>
                    <?php if ($can_edit): ?>
                    <div class="mt-2 d-flex align-items-center gap-1">
                        <button type="button" @click="openBulkApply(sp)" class="btn btn-sm" style="background:transparent;border:1px solid #7A9E7E;color:#7A9E7E;font-size:.62rem;padding:.1rem .4rem;font-weight:500;">
                            <i class="fas fa-project-diagram me-1"></i>Apply to sites
                        </button>
                        <button type="button" @click="openSpecEdit(sp)" class="btn btn-sm" style="background:transparent;border:none;color:#7A8BA1;padding:0 .3rem;font-size:.66rem;"><i class="fas fa-pencil-alt"></i></button>
                        <button type="button" @click="removeSpec(sp)" class="btn btn-sm" style="background:transparent;border:none;color:#C48579;padding:0 .3rem;font-size:.66rem;"><i class="fas fa-times"></i></button>
                    </div>
                    <?php endif; ?>
                </div>
            </div>
        </div>
    </div>

    <!-- Assets import panel · Vue3 · 8Z-z-B -->
    <div v-if="assetImportOpen" class="card border-0 shadow-sm mb-3" style="border-left:3px solid #7A9E7E !important;">
        <div class="card-header bg-white d-flex align-items-center justify-content-between" style="border-bottom:1px solid #E2E8F0;">
            <div>
                <strong style="color:#334155;"><i class="fas fa-file-upload me-2" style="color:#7A9E7E;"></i>Import equipment from Excel</strong>
                <div class="text-muted small mt-1" style="color:#94A3B8;font-size:.68rem;">
                    <code style="color:#7A8BA1;background:#EEF2F6;padding:0 .2rem;">site_code</code> is required (resolves to the site) ·
                    <code style="color:#7A8BA1;background:#EEF2F6;padding:0 .2rem;">spec_code</code> optional (inherits blueprint · overridable per-column) ·
                    otherwise use <code style="color:#7A8BA1;background:#EEF2F6;padding:0 .2rem;">asset_type_code</code> (GEN / AC / UPS / …).
                </div>
            </div>
            <button type="button" @click="closeAssetImport" class="btn btn-sm" style="background:transparent;border:none;color:#94A3B8;">
                <i class="fas fa-times"></i>
            </button>
        </div>
        <div class="card-body p-3">
            <div class="alert py-2 mb-3" style="background:#F8FAFC;border:1px solid #E2E8F0;color:#334155;font-size:.72rem;">
                <i class="fas fa-info-circle me-1" style="color:#7A9E7E;"></i>
                <a href="#" @click.prevent="downloadAssetTemplate" style="color:#7A9E7E;font-weight:600;text-decoration:none;">Download the sample template</a> — 5 example rows across GEN / AC / UPS plus an Instructions sheet.
            </div>

            <div class="row g-2 align-items-end">
                <div class="col-md-8">
                    <label class="form-label small text-muted" style="font-size:.68rem;text-transform:uppercase;letter-spacing:.4px;font-weight:600;">Excel file</label>
                    <input type="file" ref="assetImportFile" @change="onAssetImportFileChange" class="form-control form-control-sm" accept=".xlsx,.xls,.csv">
                </div>
                <div class="col-md-4 text-md-end">
                    <span v-if="assetImportFileName" class="small" style="color:#64748B;font-size:.72rem;">
                        <i class="fas fa-file-excel me-1" style="color:#7A9E7E;"></i>{{ assetImportFileName }}
                        <span v-if="assetImportRows.length" style="color:#94A3B8;">· {{ assetImportRows.length }} row{{ assetImportRows.length === 1 ? '' : 's' }}</span>
                    </span>
                </div>
            </div>

            <div v-if="assetImportParseError" class="alert py-2 mt-3 mb-0" style="background:#FBEEEA;border:1px solid #C48579;color:#8A4A3A;font-size:.78rem;">
                <i class="fas fa-exclamation-triangle me-1"></i>{{ assetImportParseError }}
            </div>

            <div v-if="assetImportRows.length" class="mt-3">
                <div class="small mb-2" style="color:#334155;font-weight:600;font-size:.74rem;">
                    Preview
                    <span v-if="assetImportRows.length > 10" style="color:#94A3B8;font-weight:400;">· showing first 10 of {{ assetImportRows.length }}</span>
                </div>
                <div style="max-height:280px;overflow:auto;border:1px solid #E2E8F0;border-radius:4px;">
                    <table class="table table-sm mb-0" style="font-size:.7rem;">
                        <thead style="background:#F8FAFC;position:sticky;top:0;">
                            <tr style="color:#64748B;font-size:.62rem;text-transform:uppercase;letter-spacing:.3px;">
                                <th v-for="col in assetImportPreviewCols" :key="'aic_'+col">{{ col }}</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr v-for="(r, i) in assetImportRows.slice(0, 10)" :key="'air_'+i">
                                <td v-for="col in assetImportPreviewCols" :key="'airc_'+i+'_'+col" style="color:#334155;">{{ r[col] }}</td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>

            <div class="mt-3 d-flex align-items-center justify-content-end gap-2">
                <button type="button" @click="closeAssetImport" class="btn btn-sm" style="background:transparent;border:1px solid #E2E8F0;color:#64748B;font-weight:500;">Cancel</button>
                <button type="button" @click="runAssetImport" :disabled="!assetImportRows.length || assetImportBusy"
                        class="btn btn-sm" style="background:#7A9E7E;border-color:#7A9E7E;color:#fff;font-weight:600;">
                    <i class="fas" :class="assetImportBusy ? 'fa-spinner fa-spin' : 'fa-check'"></i>
                    <span class="ms-1">{{ assetImportBusy ? 'Importing…' : ('Import ' + assetImportRows.length + ' row(s)') }}</span>
                </button>
            </div>
        </div>
    </div>

    <!-- Bulk-apply blueprint panel · Vue3 · 8Z-z-B -->
    <div v-if="bulkApplyOpen && bulkApplySpec" class="card border-0 shadow-sm mb-3" style="border-left:3px solid #7A9E7E !important;">
        <div class="card-header bg-white d-flex align-items-center justify-content-between" style="border-bottom:1px solid #E2E8F0;">
            <div>
                <strong style="color:#334155;">
                    <i class="fas fa-project-diagram me-2" style="color:#7A9E7E;"></i>
                    Apply blueprint <code style="color:#D4A574;font-size:.8rem;background:#FBF4EA;padding:.1rem .3rem;border-radius:2px;">{{ bulkApplySpec.spec_code }}</code>
                    to sites
                </strong>
                <div class="text-muted small mt-1" style="color:#94A3B8;font-size:.68rem;">
                    Creates <strong>{{ bulkApplyQty }} × {{ bulkApplySelected.length }} = {{ bulkApplyQty * bulkApplySelected.length }}</strong> asset row(s). Auto-generated asset_no format: <code>{{ bulkApplySpec.spec_code }}-&lt;site_code&gt;-NN</code>.
                </div>
            </div>
            <button type="button" @click="closeBulkApply" class="btn btn-sm" style="background:transparent;border:none;color:#94A3B8;">
                <i class="fas fa-times"></i>
            </button>
        </div>
        <div class="card-body p-3">
            <div class="row g-2 align-items-center mb-3">
                <div class="col-md-3">
                    <label class="form-label small text-muted" style="font-size:.68rem;text-transform:uppercase;letter-spacing:.4px;font-weight:600;">Qty per site</label>
                    <input type="number" v-model.number="bulkApplyQty" min="1" max="20" class="form-control form-control-sm" style="max-width:100px;">
                </div>
                <div class="col-md-9 text-md-end">
                    <button type="button" @click="bulkApplySelectAll" class="btn btn-sm" style="background:transparent;border:1px solid #7A8BA1;color:#7A8BA1;font-weight:500;">Select all ({{ sites.length }})</button>
                    <button type="button" @click="bulkApplySelectNone" class="btn btn-sm" style="background:transparent;border:1px solid #E2E8F0;color:#64748B;font-weight:500;">Clear</button>
                </div>
            </div>

            <div style="max-height:320px;overflow:auto;border:1px solid #E2E8F0;border-radius:4px;">
                <table class="table table-sm mb-0" style="font-size:.74rem;">
                    <thead style="background:#F8FAFC;position:sticky;top:0;">
                        <tr style="color:#64748B;font-size:.62rem;text-transform:uppercase;letter-spacing:.3px;">
                            <th style="width:40px;"></th>
                            <th style="width:110px;">Code</th>
                            <th>Name</th>
                            <th style="width:130px;">Region</th>
                            <th style="width:110px;text-align:center;">Existing units</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr v-for="s in sites" :key="'bs_'+s.c0d" @click="bulkApplyToggle(s.c0d)" style="cursor:pointer;">
                            <td><input type="checkbox" :checked="bulkApplySelected.includes(s.c0d)" @click.stop="bulkApplyToggle(s.c0d)"></td>
                            <td><code style="color:#7A8BA1;font-size:.7rem;">{{ s.site_code || '—' }}</code></td>
                            <td style="color:#334155;">{{ s.name }}</td>
                            <td style="color:#64748B;">{{ s.region }}</td>
                            <td style="text-align:center;color:#334155;">{{ assetsFor(s.c0d).length }}</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <div class="mt-3 d-flex align-items-center justify-content-end gap-2">
                <button type="button" @click="closeBulkApply" class="btn btn-sm" style="background:transparent;border:1px solid #E2E8F0;color:#64748B;font-weight:500;">Cancel</button>
                <button type="button" @click="runBulkApply" :disabled="!bulkApplySelected.length || bulkApplyBusy"
                        class="btn btn-sm" style="background:#7A9E7E;border-color:#7A9E7E;color:#fff;font-weight:600;">
                    <i class="fas" :class="bulkApplyBusy ? 'fa-spinner fa-spin' : 'fa-check'"></i>
                    <span class="ms-1">{{ bulkApplyBusy ? 'Applying…' : ('Create ' + (bulkApplyQty * bulkApplySelected.length) + ' asset(s)') }}</span>
                </button>
            </div>
        </div>
    </div>

    <!-- Toolbar + sites table -->
    <div class="card border-0 shadow-sm mb-3">
        <div class="card-header bg-white d-flex align-items-center justify-content-between" style="border-bottom:1px solid #E2E8F0;">
            <div>
                <strong style="color:#334155;"><i class="fas fa-map-marked-alt me-2" style="color:#7A9E7E;"></i>Sites register</strong>
                <span class="text-muted ms-2" style="color:#94A3B8;font-size:.68rem;">Click a row to manage POCs &amp; installed equipment.</span>
            </div>
            <?php if ($can_edit): ?>
            <div>
                <button type="button" @click="downloadTemplate" class="btn btn-sm" style="background:transparent;border:1px solid #7A8BA1;color:#7A8BA1;font-weight:500;">
                    <i class="fas fa-file-download me-1"></i>Template
                </button>
                <button type="button" @click="openImport" class="btn btn-sm" style="background:transparent;border:1px solid #7A9E7E;color:#7A9E7E;font-weight:500;">
                    <i class="fas fa-file-upload me-1"></i>Import Excel
                </button>
                <button type="button" @click="openSiteAdd" class="btn btn-sm" style="background:#7A9E7E;border-color:#7A9E7E;color:#fff;font-weight:600;">
                    <i class="fas fa-plus me-1"></i>Add site
                </button>
            </div>
            <?php endif; ?>
        </div>
        <div class="card-body p-0">
            <div v-if="!sites.length" class="p-4 text-center" style="color:#94A3B8;font-size:.82rem;">
                No sites yet. Click <strong>Add site</strong> or <strong>Import Excel</strong> to start.
            </div>
            <table v-else class="table table-sm mb-0" style="font-size:.78rem;">
                <thead style="background:#F8FAFC;">
                    <tr style="color:#64748B;font-size:.66rem;text-transform:uppercase;letter-spacing:.4px;">
                        <th style="width:110px;">Code</th>
                        <th>Name</th>
                        <th style="width:130px;">Region</th>
                        <th style="width:110px;text-align:center;">POCs</th>
                        <th style="width:110px;text-align:center;">Equipment</th>
                        <th style="width:140px;">Status</th>
                        <?php if ($can_edit): ?><th style="width:70px;"></th><?php endif; ?>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="s in sites" :key="'s_'+s.c0d" style="cursor:pointer;" @click="gotoSite(s)">
                        <td><code style="color:#7A8BA1;font-size:.72rem;">{{ s.site_code || '—' }}</code></td>
                        <td style="color:#334155;font-weight:500;">{{ s.name }}</td>
                        <td style="color:#64748B;">{{ s.region }}<span v-if="s.district" style="color:#94A3B8;font-size:.68rem;"> · {{ s.district }}</span></td>
                        <td style="text-align:center;color:#334155;">{{ pocsFor(s.c0d).length }}</td>
                        <td style="text-align:center;color:#334155;">{{ assetsFor(s.c0d).length }}</td>
                        <td>
                            <span v-if="s.poc_confirmed_on" class="badge" style="background:#7A9E7E;color:#fff;font-size:.6rem;font-weight:600;padding:.15rem .5rem;text-transform:capitalize;">
                                <i class="fas fa-check-circle me-1"></i>POC confirmed
                            </span>
                            <span v-else-if="pocsFor(s.c0d).length" class="badge" style="background:#D4A574;color:#fff;font-size:.6rem;font-weight:600;padding:.15rem .5rem;text-transform:capitalize;">
                                <i class="fas fa-clock me-1"></i>POC pending
                            </span>
                            <span v-else class="badge" style="background:#C48579;color:#fff;font-size:.6rem;font-weight:600;padding:.15rem .5rem;text-transform:capitalize;">
                                <i class="fas fa-exclamation-triangle me-1"></i>No POC
                            </span>
                        </td>
                        <?php if ($can_edit): ?>
                        <td style="text-align:right;" @click.stop>
                            <button type="button" @click="openSiteEdit(s)" class="btn btn-sm" style="background:transparent;border:none;color:#7A8BA1;padding:0 .3rem;"><i class="fas fa-pencil-alt"></i></button>
                            <button type="button" @click="removeSite(s)" class="btn btn-sm" style="background:transparent;border:none;color:#C48579;padding:0 .3rem;"><i class="fas fa-times"></i></button>
                        </td>
                        <?php endif; ?>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>

    <!-- Site drawer -->
    <div v-if="drawerSite" class="card border-0 shadow-sm mb-3" style="border-left:3px solid #7A8BA1 !important;">
        <div class="card-header bg-white d-flex align-items-center justify-content-between" style="border-bottom:1px solid #E2E8F0;">
            <div>
                <strong style="color:#334155;">
                    <i class="fas fa-map-pin me-2" style="color:#7A8BA1;"></i>
                    <code style="color:#7A8BA1;font-size:.78rem;background:#EEF2F6;padding:.1rem .35rem;border-radius:2px;">{{ drawerSite.site_code || '—' }}</code>
                    <span class="ms-2">{{ drawerSite.name }}</span>
                </strong>
                <div class="text-muted small mt-1" style="color:#64748B;font-size:.7rem;">
                    <span v-if="drawerSite.region">{{ drawerSite.region }}</span>
                    <span v-if="drawerSite.district"> · {{ drawerSite.district }}</span>
                    <span v-if="drawerSite.physical_address"> · {{ drawerSite.physical_address }}</span>
                    <span v-if="drawerSite.gps_lat && drawerSite.gps_lng" class="ms-2">
                        · <a :href="'https://maps.google.com/?q=' + drawerSite.gps_lat + ',' + drawerSite.gps_lng" target="_blank" style="color:#7A8BA1;text-decoration:none;">
                            <i class="fas fa-external-link-alt" style="font-size:.62rem;"></i> Map
                        </a>
                    </span>
                </div>
            </div>
            <button type="button" @click="closeDrawer" class="btn btn-sm" style="background:transparent;border:none;color:#94A3B8;">
                <i class="fas fa-times"></i>
            </button>
        </div>
        <div class="card-body p-0">

            <!-- POCs -->
            <div class="p-3" style="border-bottom:1px solid #E2E8F0;">
                <div class="d-flex align-items-center justify-content-between mb-2">
                    <div>
                        <strong style="color:#334155;font-size:.86rem;">Points of contact</strong>
                        <span v-if="drawerSite.poc_confirmed_on" class="badge ms-2" style="background:#7A9E7E;color:#fff;font-size:.6rem;font-weight:600;padding:.15rem .5rem;">
                            <i class="fas fa-check-circle me-1"></i>Confirmed {{ fmtWhen(drawerSite.poc_confirmed_on) }}
                        </span>
                    </div>
                    <?php if ($can_edit): ?>
                    <div>
                        <button v-if="!drawerSite.poc_confirmed_on" type="button" @click="confirmPocs(drawerSite)"
                                :disabled="!pocsFor(drawerSite.c0d).length && !projectPocs.length"
                                :title="!pocsFor(drawerSite.c0d).length && !projectPocs.length ? 'Add a site POC or a project-level POC first' : ''"
                                class="btn btn-sm" style="background:#7A9E7E;border-color:#7A9E7E;color:#fff;font-weight:600;">
                            <i class="fas fa-check me-1"></i>Confirm POCs
                        </button>
                        <button v-else type="button" @click="unconfirmPocs(drawerSite)"
                                class="btn btn-sm" style="background:transparent;border:1px solid #C48579;color:#C48579;font-weight:500;">
                            <i class="fas fa-undo me-1"></i>Un-confirm
                        </button>
                        <button type="button" @click="openPocAdd(drawerSite.c0d)" class="btn btn-sm ms-1" style="background:transparent;border:1px solid #7A8BA1;color:#7A8BA1;font-weight:500;">
                            <i class="fas fa-plus me-1"></i>Add POC
                        </button>
                    </div>
                    <?php endif; ?>
                </div>
                <!-- Inherited from project (read-only) -->
                <div v-if="projectPocs.length" class="mb-2 p-2" style="background:#F1F5F9;border:1px dashed #CBD5E1;border-radius:4px;">
                    <div class="small mb-1" style="color:#64748B;font-size:.62rem;text-transform:uppercase;letter-spacing:.4px;font-weight:600;">
                        <i class="fas fa-arrow-turn-down me-1"></i>Inherited from project · {{ projectPocs.length }} default POC{{ projectPocs.length === 1 ? '' : 's' }}
                    </div>
                    <div class="d-flex flex-wrap gap-2">
                        <span v-for="pc in projectPocs" :key="'ipc_'+pc.c0d"
                              class="badge d-inline-flex align-items-center gap-1" style="background:#fff;color:#334155;border:1px solid #CBD5E1;font-size:.66rem;font-weight:500;padding:.25rem .5rem;">
                            <span class="badge" :style="'background:'+projectRoleColor(pc.poc_role)+';color:#fff;font-size:.52rem;font-weight:600;padding:.08rem .3rem;text-transform:capitalize;'">{{ projectRoleLabel(pc.poc_role) }}</span>
                            <strong style="color:#334155;font-weight:600;">{{ pc.poc_name }}</strong>
                            <span v-if="pc.poc_phone" style="color:#64748B;font-size:.6rem;">· {{ pc.poc_phone }}</span>
                        </span>
                    </div>
                </div>

                <div v-if="!pocsFor(drawerSite.c0d).length" class="p-2 text-center" style="color:#94A3B8;font-size:.72rem;">
                    <span v-if="projectPocs.length">No site-specific POC. The project defaults above apply.</span>
                    <span v-else>No POCs on file. PM / PC needs at least one to confirm the site.</span>
                </div>
                <table v-else class="table table-sm mb-0" style="font-size:.76rem;background:transparent;">
                    <tbody>
                        <tr v-for="pc in pocsFor(drawerSite.c0d)" :key="'p_'+pc.c0d">
                            <td style="width:130px;">
                                <span class="badge" :style="'background:'+roleColor(pc.poc_role)+';color:#fff;font-size:.58rem;font-weight:600;padding:.15rem .4rem;text-transform:capitalize;'">
                                    {{ roleLabel(pc.poc_role) }}
                                </span>
                                <span v-if="pc.is_primary == 1" class="badge ms-1" style="background:#7A9E7E;color:#fff;font-size:.55rem;font-weight:600;padding:.1rem .3rem;">PRIMARY</span>
                            </td>
                            <td style="color:#334155;font-weight:500;">{{ pc.poc_name }}</td>
                            <td style="color:#64748B;font-size:.72rem;">
                                <a v-if="pc.poc_phone" :href="'tel:' + pc.poc_phone" style="color:#64748B;text-decoration:none;"><i class="fas fa-phone me-1" style="font-size:.6rem;"></i>{{ pc.poc_phone }}</a>
                            </td>
                            <td style="color:#64748B;font-size:.72rem;">
                                <a v-if="pc.poc_email" :href="'mailto:' + pc.poc_email" style="color:#64748B;text-decoration:none;"><i class="fas fa-envelope me-1" style="font-size:.6rem;"></i>{{ pc.poc_email }}</a>
                            </td>
                            <?php if ($can_edit): ?>
                            <td style="width:60px;text-align:right;">
                                <button type="button" @click="openPocEdit(pc)" class="btn btn-sm" style="background:transparent;border:none;color:#7A8BA1;padding:0 .3rem;"><i class="fas fa-pencil-alt"></i></button>
                                <button type="button" @click="removePoc(pc)" class="btn btn-sm" style="background:transparent;border:none;color:#C48579;padding:0 .3rem;"><i class="fas fa-times"></i></button>
                            </td>
                            <?php endif; ?>
                        </tr>
                    </tbody>
                </table>
            </div>

            <!-- Installed equipment (8Z-z-B) -->
            <div class="p-3">
                <div class="d-flex align-items-center justify-content-between mb-2">
                    <div>
                        <strong style="color:#334155;font-size:.86rem;">Installed equipment</strong>
                        <span class="text-muted small ms-2" style="color:#94A3B8;font-size:.68rem;">{{ assetsFor(drawerSite.c0d).length }} unit{{ assetsFor(drawerSite.c0d).length === 1 ? '' : 's' }} on file</span>
                    </div>
                    <?php if ($can_edit): ?>
                    <div>
                        <button type="button" @click="openAssetAdd(drawerSite.c0d)" class="btn btn-sm" style="background:#7A9E7E;border-color:#7A9E7E;color:#fff;font-weight:600;">
                            <i class="fas fa-plus me-1"></i>Add equipment
                        </button>
                    </div>
                    <?php endif; ?>
                </div>
                <div v-if="!assetsFor(drawerSite.c0d).length" class="p-2 text-center" style="color:#94A3B8;font-size:.72rem;">
                    No equipment logged at this site yet. Use <strong>Add equipment</strong>, or apply a blueprint from the project-level card.
                </div>
                <table v-else class="table table-sm mb-0" style="font-size:.74rem;">
                    <thead style="background:#F8FAFC;">
                        <tr style="color:#64748B;font-size:.62rem;text-transform:uppercase;letter-spacing:.4px;">
                            <th>Type</th>
                            <th>Asset no</th>
                            <th>Serial</th>
                            <th>Make/model</th>
                            <th style="text-align:right;">Size</th>
                            <th>Install date</th>
                            <th>Spec</th>
                            <?php if ($can_edit): ?><th style="width:70px;"></th><?php endif; ?>
                        </tr>
                    </thead>
                    <tbody>
                        <tr v-for="a in assetsFor(drawerSite.c0d)" :key="'a_'+a.c0d">
                            <td><i :class="'fas ' + (a.asset_icon || 'fa-cube')" style="color:#7A8BA1;font-size:.72rem;"></i> <span style="color:#334155;">{{ a.asset_type_name }}</span></td>
                            <td><code style="color:#7A8BA1;font-size:.7rem;">{{ a.asset_no || '—' }}</code></td>
                            <td style="color:#64748B;">{{ a.serial_no }}</td>
                            <td style="color:#64748B;">{{ [a.make, a.model].filter(Boolean).join(' ') }}</td>
                            <td style="text-align:right;color:#334155;font-family:monospace;">{{ a.size_value }} <span style="color:#94A3B8;font-size:.66rem;">{{ a.size_unit }}</span></td>
                            <td style="color:#64748B;">{{ a.install_date }}</td>
                            <td>
                                <code v-if="a.spec_id" style="color:#7A9E7E;font-size:.62rem;background:#EAF2E8;padding:.05rem .25rem;border-radius:2px;">{{ specCodeFor(a.spec_id) }}</code>
                                <span v-else style="color:#94A3B8;font-size:.62rem;">—</span>
                            </td>
                            <?php if ($can_edit): ?>
                            <td style="text-align:right;">
                                <button type="button" @click="openAssetEdit(a)" class="btn btn-sm" style="background:transparent;border:none;color:#7A8BA1;padding:0 .3rem;"><i class="fas fa-pencil-alt"></i></button>
                                <button type="button" @click="removeAsset(a)" class="btn btn-sm" style="background:transparent;border:none;color:#C48579;padding:0 .3rem;"><i class="fas fa-times"></i></button>
                            </td>
                            <?php endif; ?>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</div>

<script src="<?= PROOT ?>js/Olibs/vue.global.prod.js"></script>
<script src="<?= PROOT ?>js/Olibs/xlsx.full.min.js"></script>
<script>
(function () {
    var CID       = <?= (int)$contractId; ?>;
    var PID       = <?= (int)$p['c0d']; ?>;
    var PROOT     = <?= json_encode(PROOT); ?>;
    var canEdit   = <?= !empty($can_edit) ? 'true' : 'false'; ?>;
    var PROJECT_NO = <?= json_encode($p['project_no'] ?? ''); ?>;
    var bootSites       = <?= json_encode($sites        ?? [], JSON_UNESCAPED_SLASHES); ?>;
    var bootPocs        = <?= json_encode($pocs         ?? [], JSON_UNESCAPED_SLASHES); ?>;
    var bootProjectPocs = <?= json_encode($project_pocs ?? [], JSON_UNESCAPED_SLASHES); ?>;
    var bootAssets      = <?= json_encode($assets       ?? [], JSON_UNESCAPED_SLASHES); ?>;
    var bootAssetSpecs  = <?= json_encode($asset_specs  ?? [], JSON_UNESCAPED_SLASHES); ?>;
    var ASSET_TYPES     = <?= json_encode(array_map(fn($t) => ['c0d'=>(int)$t['c0d'],'code'=>$t['code'],'name'=>$t['name'],'icon'=>$t['icon'] ?? 'fa-cube'], $asset_types ?? []), JSON_UNESCAPED_SLASHES); ?>;

    if (typeof Vue === 'undefined') {
        $('#sites_root').removeAttr('v-cloak').css('display','block');
        $('#sites_root').prepend('<div class="alert alert-warning small">Sites UI could not initialise (Vue failed to load).</div>');
        return;
    }

    var esc = function (s) { return String(s == null ? '' : s).replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;'); };
    var ROLE_LABEL = { SITE_MANAGER:'Site manager', TECH:'Tech', SECURITY:'Security', OPS:'Ops', OTHER:'Other' };
    var ROLE_COLOR = { SITE_MANAGER:'#7A9E7E', TECH:'#7A8BA1', SECURITY:'#94A3B8', OPS:'#D4A574', OTHER:'#64748B' };
    // Project-level POC role vocabulary — different set from site POCs
    var P_ROLE_LABEL = { ACCOUNT_MANAGER:'Account manager', REGIONAL_OPS:'Regional ops', TECH_LEAD:'Tech lead', FINANCE:'Finance', ESCALATION:'Escalation', OTHER:'Other' };
    var P_ROLE_COLOR = { ACCOUNT_MANAGER:'#7A8BA1', REGIONAL_OPS:'#7A9E7E', TECH_LEAD:'#5B7A9E', FINANCE:'#D4A574', ESCALATION:'#C48579', OTHER:'#64748B' };

    var app = Vue.createApp({
        data: function () {
            return {
                CID: CID, PID: PID, PROOT: PROOT, canEdit: canEdit,
                sites:       (bootSites       || []).slice(),
                pocs:        (bootPocs        || []).slice(),
                projectPocs: (bootProjectPocs || []).slice(),
                assets:      (bootAssets      || []).slice(),
                assetSpecs:  (bootAssetSpecs  || []).slice(),
                assetTypes:  (ASSET_TYPES     || []).slice(),
                drawerSite: null,
                // Bulk-apply blueprint panel
                bulkApplyOpen: false,
                bulkApplySpec: null,
                bulkApplyQty: 1,
                bulkApplySelected: [],
                bulkApplyBusy: false,
                // Assets Excel import panel
                assetImportOpen: false,
                assetImportRows: [],
                assetImportFileName: '',
                assetImportParseError: '',
                assetImportBusy: false,
                // Slice 8Z-z-A · inline Excel import panel state (Vue Options API,
                // replaces the earlier jquery-confirm modal).
                importOpen: false,
                importRows: [],
                importFileName: '',
                importParseError: '',
                importBusy: false
            };
        },
        computed: {
            confirmedCount: function () { return this.sites.filter(function (s) { return !!s.poc_confirmed_on; }).length; },
            progressPct: function () {
                if (!this.sites.length) return 0;
                var withPoc = this.sites.filter(function (s) { return !!s.poc_confirmed_on; }).length;
                return Math.round((withPoc / this.sites.length) * 100);
            },
            /** Columns to preview from the parsed sheet — first 6 keys of the first row, in original order. */
            importPreviewCols: function () {
                if (!this.importRows.length) return [];
                return Object.keys(this.importRows[0]).slice(0, 6);
            },
            assetImportPreviewCols: function () {
                if (!this.assetImportRows.length) return [];
                return Object.keys(this.assetImportRows[0]).slice(0, 7);
            }
        },
        methods: {
            roleLabel: function (r) { return ROLE_LABEL[r] || r; },
            roleColor: function (r) { return ROLE_COLOR[r] || '#64748B'; },
            projectRoleLabel: function (r) { return P_ROLE_LABEL[r] || r; },
            projectRoleColor: function (r) { return P_ROLE_COLOR[r] || '#64748B'; },
            pocsFor:   function (siteId) { return this.pocs.filter(function (p) { return (p.site_id | 0) === (siteId | 0); }); },
            assetsFor: function (siteId) { return this.assets.filter(function (a) { return (a.site_id | 0) === (siteId | 0); }); },
            fmtWhen: function (iso) {
                if (!iso) return '';
                var d = new Date(String(iso).replace(' ', 'T'));
                if (isNaN(d.getTime())) return iso;
                var M = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
                return d.getDate() + ' ' + M[d.getMonth()];
            },
            openDrawer: function (s) { this.drawerSite = s; window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); },
            gotoSite: function (s) { window.location.href = PROOT + 'Projects/site/' + (s.c0d | 0); },
            closeDrawer: function () { this.drawerSite = null; },

            refresh: function () {
                var self = this;
                $.post(PROOT + 'Projects/sitesSnapshotAj', { contract_id: self.CID }, 'json').done(function (res) {
                    if (!res || !res.ok) return;
                    self.sites       = res.sites        || [];
                    self.pocs        = res.pocs         || [];
                    self.projectPocs = res.project_pocs || [];
                    self.assets      = res.assets       || [];
                    self.assetSpecs  = res.asset_specs  || [];
                    if (self.drawerSite) {
                        var still = self.sites.find(function (s) { return (s.c0d | 0) === (self.drawerSite.c0d | 0); });
                        self.drawerSite = still || null;
                    }
                });
            },
            specCodeFor: function (specId) {
                var sp = this.assetSpecs.find(function (s) { return (s.c0d | 0) === (specId | 0); });
                return sp ? sp.spec_code : '';
            },

            /* ── Project-level POCs (8Z-z-A') ──────────────────────── */
            projectPocFormHtml: function (pc) {
                pc = pc || {};
                var roles = ['ACCOUNT_MANAGER','REGIONAL_OPS','TECH_LEAD','FINANCE','ESCALATION','OTHER'];
                var opts = roles.map(function (r) { return '<option value="' + r + '"' + ((pc.poc_role || 'ACCOUNT_MANAGER') === r ? ' selected' : '') + '>' + P_ROLE_LABEL[r] + '</option>'; }).join('');
                return ''
                     + '<div class="row g-2"><div class="col-5"><label class="form-label small text-muted">Role</label><select id="pp_role" class="form-select form-select-sm">' + opts + '</select></div>'
                     + '<div class="col-7"><label class="form-label small text-muted">Name *</label><input type="text" id="pp_name" class="form-control form-control-sm" value="' + esc(pc.poc_name) + '"></div></div>'
                     + '<div class="row g-2 mt-1"><div class="col-6"><label class="form-label small text-muted">Phone</label><input type="text" id="pp_phone" class="form-control form-control-sm" value="' + esc(pc.poc_phone) + '"></div>'
                     + '<div class="col-6"><label class="form-label small text-muted">Email</label><input type="email" id="pp_email" class="form-control form-control-sm" value="' + esc(pc.poc_email) + '"></div></div>'
                     + '<div class="mt-1"><label class="form-label small text-muted">Notes</label><input type="text" id="pp_notes" class="form-control form-control-sm" value="' + esc(pc.notes) + '"></div>'
                     + '<div class="form-check mt-2"><input type="checkbox" id="pp_prim" class="form-check-input"' + ((pc.is_primary | 0) === 1 ? ' checked' : '') + '><label for="pp_prim" class="form-check-label small">Primary POC for this project</label></div>';
            },
            collectProjectPocPayload: function () {
                return { poc_role: $('#pp_role').val(), poc_name: $('#pp_name').val(),
                         poc_phone: $('#pp_phone').val(), poc_email: $('#pp_email').val(),
                         notes: $('#pp_notes').val(),
                         is_primary: $('#pp_prim').is(':checked') ? 1 : 0 };
            },
            openProjectPocAdd: function () {
                var self = this;
                $.confirm({
                    title: 'Add project-level POC', content: self.projectPocFormHtml({}), type: 'blue',
                    buttons: {
                        save: { text: 'Add', btnClass: 'btn-primary', action: function () {
                            var p = self.collectProjectPocPayload(); p.contract_id = self.CID;
                            $.post(PROOT + 'Projects/projectPocSaveAj', p, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Project POC added.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },
            openProjectPocEdit: function (pc) {
                var self = this;
                $.confirm({
                    title: 'Edit project-level POC', content: self.projectPocFormHtml(pc), type: 'blue',
                    buttons: {
                        save: { text: 'Save', btnClass: 'btn-primary', action: function () {
                            var p = self.collectProjectPocPayload(); p.contract_id = self.CID; p.c0d = pc.c0d;
                            $.post(PROOT + 'Projects/projectPocSaveAj', p, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Saved.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },
            removeProjectPoc: function (pc) {
                var self = this;
                $.confirm({
                    title: 'Remove project-level POC?', type: 'orange',
                    content: 'POC "' + esc(pc.poc_name) + '" will no longer be inherited by any site under this project.',
                    buttons: {
                        yes: { text: 'Remove', btnClass: 'btn-warning', action: function () {
                            $.post(PROOT + 'Projects/projectPocRemoveAj', { poc_id: pc.c0d }, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Removed.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },

            /* ── Equipment blueprints (8Z-z-B) ─────────────────────── */
            specFormHtml: function (sp) {
                sp = sp || {};
                var typeOpts = this.assetTypes.map(function (t) {
                    return '<option value="' + t.c0d + '"' + ((sp.asset_type_id | 0) === (t.c0d | 0) ? ' selected' : '') + '>' + esc(t.name) + ' (' + esc(t.code) + ')</option>';
                }).join('');
                return ''
                     + '<div class="row g-2"><div class="col-4"><label class="form-label small text-muted">Spec code * <span style="color:#94A3B8;">(unique)</span></label><input type="text" id="sp_code" class="form-control form-control-sm" placeholder="VDC-GEN-40" value="' + esc(sp.spec_code) + '"></div>'
                     + '<div class="col-8"><label class="form-label small text-muted">Spec name *</label><input type="text" id="sp_name" class="form-control form-control-sm" placeholder="Vodacom standard 40kVA genset" value="' + esc(sp.spec_name) + '"></div></div>'
                     + '<div class="mt-1"><label class="form-label small text-muted">Asset type *</label><select id="sp_type" class="form-select form-select-sm">' + typeOpts + '</select></div>'
                     + '<div class="row g-2 mt-1"><div class="col-6"><label class="form-label small text-muted">Make</label><input type="text" id="sp_make" class="form-control form-control-sm" placeholder="Cummins" value="' + esc(sp.make) + '"></div>'
                     + '<div class="col-6"><label class="form-label small text-muted">Model</label><input type="text" id="sp_model" class="form-control form-control-sm" placeholder="C40 D5" value="' + esc(sp.model) + '"></div></div>'
                     + '<div class="row g-2 mt-1"><div class="col-4"><label class="form-label small text-muted">Size value</label><input type="number" id="sp_sv" class="form-control form-control-sm" value="' + esc(sp.size_value) + '"></div>'
                     + '<div class="col-4"><label class="form-label small text-muted">Size unit</label><input type="text" id="sp_su" class="form-control form-control-sm" placeholder="kVA · tons · kW" value="' + esc(sp.size_unit) + '"></div>'
                     + '<div class="col-4"><label class="form-label small text-muted">Default warranty (months)</label><input type="number" id="sp_wm" class="form-control form-control-sm" value="' + esc(sp.default_warranty_months) + '"></div></div>'
                     + '<div class="mt-1"><label class="form-label small text-muted">Notes</label><textarea id="sp_notes" class="form-control form-control-sm" rows="2">' + esc(sp.notes) + '</textarea></div>';
            },
            collectSpecPayload: function () {
                return {
                    spec_code: $('#sp_code').val(), spec_name: $('#sp_name').val(),
                    asset_type_id: $('#sp_type').val(),
                    make: $('#sp_make').val(), model: $('#sp_model').val(),
                    size_value: $('#sp_sv').val(), size_unit: $('#sp_su').val(),
                    default_warranty_months: $('#sp_wm').val(),
                    notes: $('#sp_notes').val(), is_active: 1
                };
            },
            openSpecAdd: function () {
                var self = this;
                $.confirm({
                    title: 'Add equipment blueprint', content: self.specFormHtml({}), columnClass: 'medium', type: 'blue',
                    buttons: {
                        save: { text: 'Add', btnClass: 'btn-primary', action: function () {
                            var p = self.collectSpecPayload(); p.contract_id = self.CID;
                            $.post(PROOT + 'Projects/assetSpecSaveAj', p, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Blueprint added.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },
            openSpecEdit: function (sp) {
                var self = this;
                $.confirm({
                    title: 'Edit blueprint', content: self.specFormHtml(sp), columnClass: 'medium', type: 'blue',
                    buttons: {
                        save: { text: 'Save', btnClass: 'btn-primary', action: function () {
                            var p = self.collectSpecPayload(); p.contract_id = self.CID; p.c0d = sp.c0d;
                            $.post(PROOT + 'Projects/assetSpecSaveAj', p, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Saved.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },
            removeSpec: function (sp) {
                var self = this;
                var deployed = sp.units_deployed | 0;
                $.confirm({
                    title: 'Remove blueprint?', type: 'orange',
                    content: 'Blueprint "' + esc(sp.spec_code) + '" will be soft-deleted.'
                             + (deployed > 0 ? '<br><br><strong>' + deployed + ' asset(s)</strong> are currently linked to this blueprint — they stay in place but lose their spec reference.' : ''),
                    buttons: {
                        yes: { text: 'Remove', btnClass: 'btn-warning', action: function () {
                            $.post(PROOT + 'Projects/assetSpecRemoveAj', { spec_id: sp.c0d }, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Removed.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },

            /* ── Bulk-apply blueprint ──────────────────────────────── */
            openBulkApply: function (sp) {
                this.bulkApplySpec = sp;
                this.bulkApplyQty = 1;
                this.bulkApplySelected = [];
                this.bulkApplyBusy = false;
                this.bulkApplyOpen = true;
                var self = this;
                this.$nextTick(function () { window.scrollTo({ top: 0, behavior: 'smooth' }); void self; });
            },
            closeBulkApply: function () {
                this.bulkApplyOpen = false;
                this.bulkApplySpec = null;
                this.bulkApplySelected = [];
            },
            bulkApplyToggle: function (siteId) {
                var i = this.bulkApplySelected.indexOf(siteId);
                if (i >= 0) this.bulkApplySelected.splice(i, 1);
                else this.bulkApplySelected.push(siteId);
            },
            bulkApplySelectAll: function () { this.bulkApplySelected = this.sites.map(function (s) { return s.c0d | 0; }); },
            bulkApplySelectNone: function () { this.bulkApplySelected = []; },
            runBulkApply: function () {
                var self = this;
                if (!self.bulkApplySpec || !self.bulkApplySelected.length || self.bulkApplyBusy) return;
                self.bulkApplyBusy = true;
                $.post(PROOT + 'Projects/assetSpecApplyAj', {
                    spec_id: self.bulkApplySpec.c0d,
                    qty_per_site: self.bulkApplyQty,
                    site_ids: JSON.stringify(self.bulkApplySelected)
                }, 'json').done(function (res) {
                    self.bulkApplyBusy = false;
                    if (res && res.ok) {
                        if (window.rsToast) rsToast('Created ' + res.created + ' asset(s) across ' + res.sites_touched + ' site(s).', 'success', 5000);
                        self.closeBulkApply();
                        self.refresh();
                    } else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                }).fail(function (xhr) {
                    self.bulkApplyBusy = false;
                    if (window.rsToast) rsToast('Network error (HTTP ' + xhr.status + ').', 'error');
                });
            },

            /* ── Per-asset add / edit / remove ─────────────────────── */
            assetFormHtml: function (a, siteId) {
                a = a || {};
                var typeOpts = this.assetTypes.map(function (t) {
                    return '<option value="' + t.c0d + '"' + ((a.asset_type_id | 0) === (t.c0d | 0) ? ' selected' : '') + '>' + esc(t.name) + ' (' + esc(t.code) + ')</option>';
                }).join('');
                var specOpts = '<option value="">— none · manual —</option>' + this.assetSpecs.map(function (sp) {
                    return '<option value="' + sp.c0d + '"' + ((a.spec_id | 0) === (sp.c0d | 0) ? ' selected' : '') + ' data-type="' + sp.asset_type_id + '" data-make="' + esc(sp.make) + '" data-model="' + esc(sp.model) + '" data-sv="' + esc(sp.size_value) + '" data-su="' + esc(sp.size_unit) + '" data-wm="' + esc(sp.default_warranty_months) + '">' + esc(sp.spec_code) + ' · ' + esc(sp.spec_name) + '</option>';
                }).join('');
                return ''
                     + '<div class="mt-1"><label class="form-label small text-muted">Blueprint (optional — prefills fields)</label><select id="a_spec" class="form-select form-select-sm">' + specOpts + '</select></div>'
                     + '<div class="row g-2 mt-1"><div class="col-6"><label class="form-label small text-muted">Asset type *</label><select id="a_type" class="form-select form-select-sm">' + typeOpts + '</select></div>'
                     + '<div class="col-6"><label class="form-label small text-muted">Asset no</label><input type="text" id="a_no" class="form-control form-control-sm" value="' + esc(a.asset_no) + '"></div></div>'
                     + '<div class="row g-2 mt-1"><div class="col-6"><label class="form-label small text-muted">Serial no</label><input type="text" id="a_serial" class="form-control form-control-sm" value="' + esc(a.serial_no) + '"></div>'
                     + '<div class="col-3"><label class="form-label small text-muted">Make</label><input type="text" id="a_make" class="form-control form-control-sm" value="' + esc(a.make) + '"></div>'
                     + '<div class="col-3"><label class="form-label small text-muted">Model</label><input type="text" id="a_model" class="form-control form-control-sm" value="' + esc(a.model) + '"></div></div>'
                     + '<div class="row g-2 mt-1"><div class="col-3"><label class="form-label small text-muted">Size value</label><input type="number" id="a_sv" class="form-control form-control-sm" value="' + esc(a.size_value) + '"></div>'
                     + '<div class="col-3"><label class="form-label small text-muted">Size unit</label><input type="text" id="a_su" class="form-control form-control-sm" value="' + esc(a.size_unit) + '"></div>'
                     + '<div class="col-3"><label class="form-label small text-muted">Install date</label><input type="date" id="a_id" class="form-control form-control-sm" value="' + esc(a.install_date) + '"></div>'
                     + '<div class="col-3"><label class="form-label small text-muted">Warranty end</label><input type="date" id="a_we" class="form-control form-control-sm" value="' + esc(a.warranty_end) + '"></div></div>';
            },
            collectAssetPayload: function () {
                return {
                    asset_type_id: $('#a_type').val(),
                    asset_no:  $('#a_no').val(),
                    serial_no: $('#a_serial').val(),
                    make:      $('#a_make').val(),
                    model:     $('#a_model').val(),
                    size_value:$('#a_sv').val(),
                    size_unit: $('#a_su').val(),
                    install_date: $('#a_id').val(),
                    warranty_end: $('#a_we').val(),
                    is_active: 1
                };
            },
            wireSpecPrefill: function () {
                $('#a_spec').on('change', function () {
                    var opt = this.options[this.selectedIndex];
                    if (!opt || !opt.value) return;
                    if (opt.dataset.type)  $('#a_type').val(opt.dataset.type);
                    if (opt.dataset.make)  $('#a_make').val(opt.dataset.make);
                    if (opt.dataset.model) $('#a_model').val(opt.dataset.model);
                    if (opt.dataset.sv)    $('#a_sv').val(opt.dataset.sv);
                    if (opt.dataset.su)    $('#a_su').val(opt.dataset.su);
                });
            },
            openAssetAdd: function (siteId) {
                var self = this;
                $.confirm({
                    title: 'Add equipment', content: self.assetFormHtml({}, siteId), columnClass: 'medium', type: 'blue',
                    onContentReady: function () { self.wireSpecPrefill(); },
                    buttons: {
                        save: { text: 'Add', btnClass: 'btn-primary', action: function () {
                            var p = self.collectAssetPayload();
                            p.contract_id = self.CID; p.site_id = siteId;
                            var specId = $('#a_spec').val();
                            if (specId) p.spec_id = specId;
                            $.post(PROOT + 'Projects/svcAssetSaveAj', p, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Equipment added.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },
            openAssetEdit: function (a) {
                var self = this;
                $.confirm({
                    title: 'Edit equipment', content: self.assetFormHtml(a), columnClass: 'medium', type: 'blue',
                    onContentReady: function () { self.wireSpecPrefill(); },
                    buttons: {
                        save: { text: 'Save', btnClass: 'btn-primary', action: function () {
                            var p = self.collectAssetPayload();
                            p.contract_id = self.CID; p.site_id = a.site_id; p.c0d = a.c0d;
                            var specId = $('#a_spec').val();
                            if (specId) p.spec_id = specId;
                            $.post(PROOT + 'Projects/svcAssetSaveAj', p, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Saved.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },
            removeAsset: function (a) {
                var self = this;
                $.confirm({
                    title: 'Remove this equipment unit?', type: 'orange',
                    content: 'Asset ' + esc(a.asset_no || a.serial_no || '#' + a.c0d) + ' will be removed from this site.',
                    buttons: {
                        yes: { text: 'Remove', btnClass: 'btn-warning', action: function () {
                            $.post(PROOT + 'Projects/svcAssetRemoveAj', { asset_id: a.c0d }, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Removed.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },

            /* ── Assets Excel import ───────────────────────────────── */
            downloadAssetTemplate: function () {
                var wb = XLSX.utils.book_new();
                var headers = ['site_code','spec_code','asset_type_code','asset_no','serial_no','make','model','size_value','size_unit','install_date','warranty_end'];
                var samples = [
                    ['CST-001','VDC-GEN-40','GEN','GEN-CST001-01','SN-A100','Cummins','C40 D5','40','kVA','2026-06-10','2027-06-10'],
                    ['CST-001','',          'AC', 'AC-CST001-01', 'SN-B221','Midea',   'MSY-18',  '18','kBTU','2026-06-10','2027-06-10'],
                    ['CST-002','VDC-GEN-40','GEN','GEN-CST002-01','SN-A101','Cummins','C40 D5','40','kVA','2026-06-12','2027-06-12'],
                    ['DSM-018','',          'UPS','UPS-DSM018-01','SN-U430','APC',     'Smart 6K','6','kVA','2026-07-01','2028-07-01'],
                    ['MRG-041','VDC-GEN-40','GEN','GEN-MRG041-01','SN-A105','Cummins','C40 D5','40','kVA','2026-08-01','2027-08-01']
                ];
                var ws = XLSX.utils.aoa_to_sheet([headers].concat(samples));
                ws['!cols'] = [{ wch: 10 }, { wch: 14 }, { wch: 14 }, { wch: 18 }, { wch: 14 }, { wch: 12 }, { wch: 14 }, { wch: 10 }, { wch: 10 }, { wch: 12 }, { wch: 12 }];
                XLSX.utils.book_append_sheet(wb, ws, 'Equipment');

                var notes = [
                    ['Column','Required','Notes'],
                    ['site_code',      'Required', 'Must match an existing site on this project (see Sites sheet in the sites template).'],
                    ['spec_code',      'Optional', 'If set, blueprint defaults fill in make/model/size/warranty. Overridable per column.'],
                    ['asset_type_code','Required if no spec_code', 'GEN · AC · UPS · BATTERY · SHELTER · TOWER · SOLAR · CHILLER · PUMP · COMPRESSOR · FIRE · CCTV'],
                    ['asset_no',       'Optional', 'Per-unit tag (e.g. GEN-CST001-01). Client asset number.'],
                    ['serial_no',      'Optional', 'Manufacturer serial.'],
                    ['make',           'Optional', 'Overrides blueprint if set.'],
                    ['model',          'Optional', 'Overrides blueprint if set.'],
                    ['size_value',     'Optional', 'Numeric.'],
                    ['size_unit',      'Optional', 'kVA · tons · kW · kBTU · V · kWh …'],
                    ['install_date',   'Optional', 'ISO YYYY-MM-DD.'],
                    ['warranty_end',   'Optional', 'ISO YYYY-MM-DD.'],
                    ['',''],
                    ['How the importer works','',''],
                    ['','',  'Rows without a resolvable site_code are skipped.'],
                    ['','',  'Rows without spec_code AND without asset_type_code are skipped.'],
                    ['','',  'Every row is INSERTED — asset imports do not merge/dedupe by serial.'],
                    ['','',  'If a spec is used, asset.spec_id is set for later spec-based reporting.']
                ];
                var wsN = XLSX.utils.aoa_to_sheet(notes);
                wsN['!cols'] = [{ wch: 22 }, { wch: 22 }, { wch: 90 }];
                XLSX.utils.book_append_sheet(wb, wsN, 'Instructions');

                XLSX.writeFile(wb, 'Equipment_' + (PROJECT_NO || 'project') + '_template.xlsx');
                if (window.rsToast) rsToast('Template downloaded — 5 sample rows on the Equipment sheet.', 'success', 3500);
            },
            openAssetImport: function () {
                this.assetImportOpen = true;
                this.assetImportRows = [];
                this.assetImportFileName = '';
                this.assetImportParseError = '';
                this.assetImportBusy = false;
                var self = this;
                this.$nextTick(function () {
                    var el = self.$refs.assetImportFile;
                    if (el && el.scrollIntoView) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
                });
            },
            closeAssetImport: function () {
                this.assetImportOpen = false;
                this.assetImportRows = [];
                this.assetImportFileName = '';
                this.assetImportParseError = '';
                this.assetImportBusy = false;
                if (this.$refs.assetImportFile) this.$refs.assetImportFile.value = '';
            },
            onAssetImportFileChange: function (e) {
                var self = this;
                var f = e.target.files && e.target.files[0];
                if (!f) return;
                self.assetImportFileName = f.name;
                self.assetImportParseError = '';
                self.assetImportRows = [];
                var reader = new FileReader();
                reader.onload = function (ev) {
                    try {
                        var wb = XLSX.read(ev.target.result, { type: 'array' });
                        var ws = wb.Sheets[wb.SheetNames[0]];
                        var rows = XLSX.utils.sheet_to_json(ws, { defval: '' });
                        if (!rows.length) { self.assetImportParseError = 'File is empty.'; return; }
                        if (!Object.prototype.hasOwnProperty.call(rows[0], 'site_code')) {
                            self.assetImportParseError = 'Missing required "site_code" column. Column names are case-sensitive.';
                            return;
                        }
                        self.assetImportRows = rows;
                    } catch (err) {
                        self.assetImportParseError = 'Could not parse: ' + (err && err.message || err);
                    }
                };
                reader.onerror = function () { self.assetImportParseError = 'Failed to read the file.'; };
                reader.readAsArrayBuffer(f);
            },
            runAssetImport: function () {
                var self = this;
                if (!self.assetImportRows.length || self.assetImportBusy) return;
                self.assetImportBusy = true;
                $.post(PROOT + 'Projects/assetsImportAj', {
                    contract_id: self.CID,
                    rows_json: JSON.stringify(self.assetImportRows)
                }, 'json').done(function (res) {
                    self.assetImportBusy = false;
                    if (res && res.ok) {
                        if (window.rsToast) rsToast('Added ' + res.added + (res.skipped ? ' · Skipped ' + res.skipped : ''), 'success', 5000);
                        self.closeAssetImport();
                        self.refresh();
                    } else {
                        self.assetImportParseError = 'Import failed: ' + (res && res.msg || 'unknown');
                    }
                }).fail(function (xhr) {
                    self.assetImportBusy = false;
                    self.assetImportParseError = 'Network error (HTTP ' + xhr.status + ').';
                });
            },

            /* ── Sites ─────────────────────────────────────────────── */
            siteFormHtml: function (s) {
                s = s || {};
                return ''
                     + '<div class="row g-2"><div class="col-4"><label class="form-label small text-muted">Site code</label><input type="text" id="s_code" class="form-control form-control-sm" value="' + esc(s.site_code) + '"></div>'
                     + '<div class="col-8"><label class="form-label small text-muted">Site name *</label><input type="text" id="s_name" class="form-control form-control-sm" value="' + esc(s.name) + '"></div></div>'
                     + '<div class="row g-2 mt-1"><div class="col-6"><label class="form-label small text-muted">Region</label><input type="text" id="s_reg" class="form-control form-control-sm" value="' + esc(s.region) + '"></div>'
                     + '<div class="col-6"><label class="form-label small text-muted">District</label><input type="text" id="s_dis" class="form-control form-control-sm" value="' + esc(s.district) + '"></div></div>'
                     + '<div class="mt-1"><label class="form-label small text-muted">Physical address</label><input type="text" id="s_addr" class="form-control form-control-sm" value="' + esc(s.physical_address) + '"></div>'
                     + '<div class="row g-2 mt-1"><div class="col-6"><label class="form-label small text-muted">GPS lat</label><input type="number" step="0.0000001" id="s_lat" class="form-control form-control-sm" value="' + esc(s.gps_lat) + '"></div>'
                     + '<div class="col-6"><label class="form-label small text-muted">GPS lng</label><input type="number" step="0.0000001" id="s_lng" class="form-control form-control-sm" value="' + esc(s.gps_lng) + '"></div></div>'
                     + '<div class="mt-1"><label class="form-label small text-muted">Access notes</label><textarea id="s_notes" class="form-control form-control-sm" rows="2">' + esc(s.access_notes) + '</textarea></div>';
            },
            collectSitePayload: function () {
                return { site_code: $('#s_code').val(), name: $('#s_name').val(),
                         region: $('#s_reg').val(), district: $('#s_dis').val(),
                         physical_address: $('#s_addr').val(),
                         gps_lat: $('#s_lat').val(), gps_lng: $('#s_lng').val(),
                         access_notes: $('#s_notes').val(), is_active: 1 };
            },
            openSiteAdd: function () {
                var self = this;
                $.confirm({
                    title: 'Add site', content: self.siteFormHtml({}), columnClass: 'medium', type: 'blue',
                    buttons: {
                        save: { text: 'Add', btnClass: 'btn-primary', action: function () {
                            var p = self.collectSitePayload(); p.contract_id = self.CID;
                            $.post(PROOT + 'Projects/svcSiteSaveAj', p, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Site added.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },
            openSiteEdit: function (s) {
                var self = this;
                $.confirm({
                    title: 'Edit site', content: self.siteFormHtml(s), columnClass: 'medium', type: 'blue',
                    buttons: {
                        save: { text: 'Save', btnClass: 'btn-primary', action: function () {
                            var p = self.collectSitePayload(); p.contract_id = self.CID; p.c0d = s.c0d;
                            $.post(PROOT + 'Projects/svcSiteSaveAj', p, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Site saved.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },
            removeSite: function (s) {
                var self = this;
                $.confirm({
                    title: 'Remove site?', type: 'orange',
                    content: 'Site "' + esc(s.name) + '" will be removed. Cannot remove sites that have assets — remove those first.',
                    buttons: {
                        yes: { text: 'Remove', btnClass: 'btn-warning', action: function () {
                            $.post(PROOT + 'Projects/svcSiteRemoveAj', { site_id: s.c0d }, 'json').done(function (res) {
                                if (res && res.ok) {
                                    if (window.rsToast) rsToast('Removed.', 'success');
                                    if (self.drawerSite && self.drawerSite.c0d === s.c0d) self.drawerSite = null;
                                    self.refresh();
                                } else if (window.rsToast) rsToast(res && res.msg || 'Remove failed', 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },

            /* ── POCs ──────────────────────────────────────────────── */
            pocFormHtml: function (pc) {
                pc = pc || {};
                var roles = ['SITE_MANAGER','TECH','SECURITY','OPS','OTHER'];
                var opts = roles.map(function (r) { return '<option value="' + r + '"' + ((pc.poc_role || 'SITE_MANAGER') === r ? ' selected' : '') + '>' + ROLE_LABEL[r] + '</option>'; }).join('');
                return ''
                     + '<div class="row g-2"><div class="col-5"><label class="form-label small text-muted">Role</label><select id="p_role" class="form-select form-select-sm">' + opts + '</select></div>'
                     + '<div class="col-7"><label class="form-label small text-muted">Name *</label><input type="text" id="p_name" class="form-control form-control-sm" value="' + esc(pc.poc_name) + '"></div></div>'
                     + '<div class="row g-2 mt-1"><div class="col-6"><label class="form-label small text-muted">Phone</label><input type="text" id="p_phone" class="form-control form-control-sm" value="' + esc(pc.poc_phone) + '"></div>'
                     + '<div class="col-6"><label class="form-label small text-muted">Email</label><input type="email" id="p_email" class="form-control form-control-sm" value="' + esc(pc.poc_email) + '"></div></div>'
                     + '<div class="form-check mt-2"><input type="checkbox" id="p_prim" class="form-check-input"' + ((pc.is_primary | 0) === 1 ? ' checked' : '') + '><label for="p_prim" class="form-check-label small">Primary POC for this site</label></div>';
            },
            collectPocPayload: function () {
                return { poc_role: $('#p_role').val(), poc_name: $('#p_name').val(),
                         poc_phone: $('#p_phone').val(), poc_email: $('#p_email').val(),
                         is_primary: $('#p_prim').is(':checked') ? 1 : 0 };
            },
            openPocAdd: function (siteId) {
                var self = this;
                $.confirm({
                    title: 'Add POC', content: self.pocFormHtml({}), type: 'blue',
                    buttons: {
                        save: { text: 'Add', btnClass: 'btn-primary', action: function () {
                            var p = self.collectPocPayload(); p.site_id = siteId;
                            $.post(PROOT + 'Projects/sitePocSaveAj', p, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('POC added.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },
            openPocEdit: function (pc) {
                var self = this;
                $.confirm({
                    title: 'Edit POC', content: self.pocFormHtml(pc), type: 'blue',
                    buttons: {
                        save: { text: 'Save', btnClass: 'btn-primary', action: function () {
                            var p = self.collectPocPayload(); p.site_id = pc.site_id; p.c0d = pc.c0d;
                            $.post(PROOT + 'Projects/sitePocSaveAj', p, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Saved.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },
            removePoc: function (pc) {
                var self = this;
                $.confirm({
                    title: 'Remove POC?', type: 'orange',
                    content: 'POC "' + esc(pc.poc_name) + '" will be removed from this site.',
                    buttons: {
                        yes: { text: 'Remove', btnClass: 'btn-warning', action: function () {
                            $.post(PROOT + 'Projects/sitePocRemoveAj', { poc_id: pc.c0d }, 'json').done(function (res) {
                                if (res && res.ok) { if (window.rsToast) rsToast('Removed.', 'success'); self.refresh(); }
                                else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                            });
                        }},
                        cancel: { text: 'Cancel', btnClass: 'btn-light' }
                    }
                });
            },
            confirmPocs: function (s) {
                var self = this;
                $.post(PROOT + 'Projects/sitePocConfirmAj', { site_id: s.c0d }, 'json').done(function (res) {
                    if (res && res.ok) { if (window.rsToast) rsToast('POCs confirmed.', 'success'); self.refresh(); }
                    else if (window.rsToast) rsToast('Failed: ' + (res && res.msg || 'unknown'), 'error');
                });
            },
            unconfirmPocs: function (s) {
                var self = this;
                $.post(PROOT + 'Projects/sitePocConfirmAj', { site_id: s.c0d, unconfirm: 1 }, 'json').done(function (res) {
                    if (res && res.ok) { if (window.rsToast) rsToast('Un-confirmed.', 'info'); self.refresh(); }
                });
            },

            /* ── Excel round-trip (SheetJS) ────────────────────────── */
            downloadTemplate: function () {
                var wb = XLSX.utils.book_new();

                // Sheet 1 · Sites — header row + 5 realistic sample rows (Vodacom-shape:
                // Coast + Dar + Morogoro clusters, varied POC roles) so the PC has a
                // clear pattern to copy from.
                var headers = ['site_code','name','region','district','physical_address','gps_lat','gps_lng','primary_poc_name','primary_poc_phone','primary_poc_email','access_notes'];
                var samples = [
                    ['CST-001','Coast-001 Bagamoyo Central','Coast','Bagamoyo',      'Plot 42, Kaole Rd',                 '-6.4419', '38.8901','John Mwanza',   '+255712345678','john.mwanza@client.tz',   'Gate access via security at main road; escort required after 18:00'],
                    ['CST-002','Coast-002 Kibaha',           'Coast','Kibaha',        'Msangani St, near TANESCO substation','-6.7659','38.9799','Halima Said',   '+255713123456','halima.said@client.tz',   'Site key held at branch office · call ahead'],
                    ['DSM-018','Dar-018 Kinondoni Msasani',  'Dar es Salaam','Kinondoni','Msasani Peninsula, next to Sea Cliff','-6.7449','39.2755','Amos Kileo',  '+255714987654','amos.kileo@client.tz',    'BTS on tower; ladder access requires TSSR present'],
                    ['DSM-024','Dar-024 Ilala Buguruni',     'Dar es Salaam','Ilala',    'Buguruni Malapa, plot 7B',           '-6.8412','39.2650','Grace Mushi', '+255715555000','grace.mushi@client.tz',   ''],
                    ['MRG-005','Morogoro-005 Kingolwira',    'Morogoro','Morogoro Urban','Kingolwira ward, near old Sisal factory','-6.8300','37.6600','Peter Ndege','+255716111222','peter.ndege@client.tz',   'Site 40km from Morogoro town · plan half-day trip']
                ];
                var ws = XLSX.utils.aoa_to_sheet([headers].concat(samples));
                // Column widths so the sample is readable when opened.
                ws['!cols'] = [
                    { wch: 10 }, { wch: 30 }, { wch: 14 }, { wch: 18 }, { wch: 40 },
                    { wch: 11 }, { wch: 11 }, { wch: 18 }, { wch: 16 }, { wch: 24 }, { wch: 60 }
                ];
                XLSX.utils.book_append_sheet(wb, ws, 'Sites');

                // Sheet 2 · Instructions — column-by-column notes so PC + Procurement know
                // which fields are required vs optional and how the importer treats them.
                var notes = [
                    ['Column',              'Required', 'Notes'],
                    ['site_code',           'Optional', 'Client-side tower / BTS code (e.g. CST-001). If present, the row is matched against existing sites by this code — otherwise it is inserted as new.'],
                    ['name',                'Required', 'Human-readable site name. Cannot be blank; rows without a name are skipped.'],
                    ['region',              'Optional', 'Administrative region (e.g. Coast, Dar es Salaam, Morogoro).'],
                    ['district',            'Optional', 'District / council within the region.'],
                    ['physical_address',    'Optional', 'Street address or landmark description.'],
                    ['gps_lat',             'Optional', 'Decimal latitude (WGS84). Southern hemisphere is negative (e.g. -6.4419).'],
                    ['gps_lng',             'Optional', 'Decimal longitude (WGS84).'],
                    ['primary_poc_name',    'Optional', 'If set, an auto-created SITE_MANAGER POC row is added / updated for this site (marked primary). Additional POCs are added later inside the site drawer.'],
                    ['primary_poc_phone',   'Optional', 'Include country code (e.g. +255712345678).'],
                    ['primary_poc_email',   'Optional', 'Used for callout emails; must be a valid mailbox.'],
                    ['access_notes',        'Optional', 'Escort / gate / key-holder notes for field teams.'],
                    ['',''],
                    ['How the importer works','',''],
                    ['','',  'Rows are matched to existing sites by (contract + site_code). Matched rows are UPDATED. Unmatched rows are INSERTED.'],
                    ['','',  'Rows without site_code are always INSERTED as new sites.'],
                    ['','',  'Duplicate site_code values within the same file — the last row wins.'],
                    ['','',  'The importer is idempotent: re-uploading the same file has no effect after the first successful pass.']
                ];
                var wsN = XLSX.utils.aoa_to_sheet(notes);
                wsN['!cols'] = [{ wch: 22 }, { wch: 10 }, { wch: 100 }];
                XLSX.utils.book_append_sheet(wb, wsN, 'Instructions');

                XLSX.writeFile(wb, 'Sites_' + (PROJECT_NO || 'project') + '_template.xlsx');
                if (window.rsToast) rsToast('Template downloaded — 5 sample rows on the Sites sheet.', 'success', 3500);
            },
            openImport: function () {
                this.importOpen = true;
                this.importRows = [];
                this.importFileName = '';
                this.importParseError = '';
                this.importBusy = false;
                var self = this;
                this.$nextTick(function () {
                    var el = self.$refs.importFile;
                    if (el && el.scrollIntoView) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
                });
            },
            closeImport: function () {
                this.importOpen = false;
                this.importRows = [];
                this.importFileName = '';
                this.importParseError = '';
                this.importBusy = false;
                if (this.$refs.importFile) this.$refs.importFile.value = '';
            },
            onImportFileChange: function (e) {
                var self = this;
                var f = e.target.files && e.target.files[0];
                if (!f) return;
                self.importFileName = f.name;
                self.importParseError = '';
                self.importRows = [];
                var reader = new FileReader();
                reader.onload = function (ev) {
                    try {
                        var wb = XLSX.read(ev.target.result, { type: 'array' });
                        var ws = wb.Sheets[wb.SheetNames[0]];
                        var rows = XLSX.utils.sheet_to_json(ws, { defval: '' });
                        if (!rows.length) { self.importParseError = 'File is empty (no rows found on the first sheet).'; return; }
                        // Sanity check — must have a "name" column (case-sensitive per template).
                        if (!Object.prototype.hasOwnProperty.call(rows[0], 'name')) {
                            self.importParseError = 'Missing required "name" column. Did you upload the right sheet? Column names are case-sensitive.';
                            return;
                        }
                        self.importRows = rows;
                    } catch (err) {
                        self.importParseError = 'Could not parse the file: ' + (err && err.message || err);
                    }
                };
                reader.onerror = function () { self.importParseError = 'Failed to read the file.'; };
                reader.readAsArrayBuffer(f);
            },
            runImport: function () {
                var self = this;
                if (!self.importRows.length || self.importBusy) return;
                self.importBusy = true;
                $.post(PROOT + 'Projects/sitesImportAj', {
                    contract_id: self.CID,
                    rows_json: JSON.stringify(self.importRows)
                }, 'json').done(function (res) {
                    self.importBusy = false;
                    if (res && res.ok) {
                        if (window.rsToast) rsToast('Added ' + res.added + ' · Updated ' + res.updated + (res.skipped ? ' · Skipped ' + res.skipped : ''), 'success', 5000);
                        self.closeImport();
                        self.refresh();
                    } else {
                        self.importParseError = 'Import failed: ' + (res && res.msg || 'unknown');
                        if (window.rsToast) rsToast(self.importParseError, 'error');
                    }
                }).fail(function (xhr) {
                    self.importBusy = false;
                    self.importParseError = 'Network error (HTTP ' + xhr.status + ').';
                });
            }
        }
    });

    $(function () {
        if (!document.getElementById('sites_root')) return;
        try { app.mount('#sites_root'); }
        catch (e) {
            console.error('[sites-vue] mount threw:', e);
            $('#sites_root').removeAttr('v-cloak').css('display','block');
            $('#sites_root').prepend('<div class="alert alert-danger small m-3">Sites UI failed to mount: ' + (e && e.message || e) + '</div>');
        }
    });
})();
</script>

<?php endif; ?>

<?php $this->end(); ?>
