Autosave: 20260724-091841
This commit is contained in:
parent
23af7294d3
commit
5437326dee
@ -1081,7 +1081,7 @@ require_once 'layout_header.php';
|
||||
<h1 class="h2 fw-bold mb-1"><i class="bi bi-headset text-primary"></i> <?php echo htmlspecialchars($callCenterSectionLabel); ?></h1>
|
||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||
<?php if ($isAdmin): ?>
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('gestiones_callcenter.php', $callCenterParams)); ?>" class="btn btn-sm btn-outline-primary">Base de Datos Pedidos</a>
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('gestiones_callcenter.php', $callCenterBaseParams)); ?>" class="btn btn-sm btn-outline-primary">Base de Datos Pedidos</a>
|
||||
<?php endif; ?>
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', $callCenterParams)); ?>" class="btn btn-sm btn-primary"><?php echo htmlspecialchars($callCenterSectionLabel); ?></a>
|
||||
</div>
|
||||
@ -1167,8 +1167,9 @@ require_once 'layout_header.php';
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-flex gap-2">
|
||||
<div class="col-auto d-flex flex-wrap gap-2">
|
||||
<button type="submit" class="btn btn-primary">Aplicar filtro</button>
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('manage_users.php', ['role' => 'Asesor']) . '#crear-asesora'); ?>" class="btn btn-outline-primary">Agregar asesora</a>
|
||||
<?php if ($selectedAssessorFilter !== ''): ?>
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('call_center_pro.php', array_merge($callCenterBaseParams, ['assessor' => 'TODAS']))); ?>" class="btn btn-outline-secondary">Ver todas</a>
|
||||
<?php endif; ?>
|
||||
@ -1368,7 +1369,7 @@ require_once 'layout_header.php';
|
||||
: ($assignedAssessorKey !== null ? 'Guardar asignación' : 'Desasignar');
|
||||
?>
|
||||
|
||||
<form method="post" class="<?php echo htmlspecialchars($assignFormClass); ?>"<?php echo $assignFormStyle !== '' ? ' style="' . htmlspecialchars($assignFormStyle, ENT_QUOTES) . '"' : ''; ?>>
|
||||
<form method="post" class="<?php echo htmlspecialchars($assignFormClass); ?>"<?php echo $assignFormStyle !== '' ? ' style="' . htmlspecialchars($assignFormStyle, ENT_QUOTES) . '"' : ''; ?> autocomplete="off">
|
||||
<input type="hidden" name="action" value="assign_assessor">
|
||||
<input type="hidden" name="source_key" value="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
|
||||
@ -1387,7 +1388,8 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
|
||||
<div class="cc-callcenter-assign-row">
|
||||
<select name="target_assessor" class="form-select form-select-sm">
|
||||
<input type="hidden" name="target_assessor" class="js-assessor-hidden" value="<?php echo htmlspecialchars((string) $selectedAssessorKeyForSelect); ?>">
|
||||
<select class="form-select form-select-sm js-assessor-select" data-server-assessor="<?php echo htmlspecialchars((string) $selectedAssessorKeyForSelect); ?>" autocomplete="off" data-lpignore="true" data-1p-ignore="true" data-bwignore="true">
|
||||
<option value=""<?php echo ($selectedAssessorKeyForSelect === '') ? ' selected' : ''; ?>>Sin asignar</option>
|
||||
|
||||
<?php foreach ($assessors as $assessorKey => $assessor): ?>
|
||||
@ -2547,6 +2549,52 @@ if (airDroidCopyButton) {
|
||||
});
|
||||
}
|
||||
|
||||
function syncServerAssessorSelect(select) {
|
||||
if (!select) {
|
||||
return;
|
||||
}
|
||||
|
||||
const form = select.closest('form');
|
||||
const hiddenInput = form ? form.querySelector('.js-assessor-hidden') : null;
|
||||
const serverAssessor = typeof select.dataset.serverAssessor === 'string'
|
||||
? (select.dataset.serverAssessor || '')
|
||||
: (hiddenInput ? (hiddenInput.value || '') : '');
|
||||
|
||||
if ((select.value || '') !== serverAssessor) {
|
||||
select.value = serverAssessor;
|
||||
}
|
||||
|
||||
if (hiddenInput && (hiddenInput.value || '') !== (select.value || '')) {
|
||||
hiddenInput.value = select.value || '';
|
||||
}
|
||||
}
|
||||
|
||||
function syncServerAssessorSelects() {
|
||||
document.querySelectorAll('.cc-callcenter-assign-form').forEach(function (form) {
|
||||
const select = form.querySelector('.js-assessor-select');
|
||||
const hiddenInput = form.querySelector('.js-assessor-hidden');
|
||||
syncServerAssessorSelect(select);
|
||||
|
||||
if (select && hiddenInput && !form.dataset.assessorSyncBound) {
|
||||
select.addEventListener('change', function () {
|
||||
hiddenInput.value = select.value || '';
|
||||
});
|
||||
form.addEventListener('submit', function () {
|
||||
hiddenInput.value = select.value || '';
|
||||
});
|
||||
form.dataset.assessorSyncBound = '1';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
syncServerAssessorSelects();
|
||||
window.addEventListener('pageshow', syncServerAssessorSelects);
|
||||
window.addEventListener('load', syncServerAssessorSelects);
|
||||
window.requestAnimationFrame(syncServerAssessorSelects);
|
||||
window.setTimeout(syncServerAssessorSelects, 0);
|
||||
window.setTimeout(syncServerAssessorSelects, 120);
|
||||
window.setTimeout(syncServerAssessorSelects, 600);
|
||||
|
||||
const ccPanelLiveEnabled = <?php echo ($isAdmin || $isTuaniRecuperablesStore) ? 'false' : 'true'; ?>;
|
||||
if (ccPanelLiveEnabled) {
|
||||
const ccPanelRoot = document.querySelector('[data-cc-panel-root="1"]');
|
||||
|
||||
@ -378,13 +378,11 @@ try {
|
||||
cc_test_ensure_tracking_table($pdo);
|
||||
cc_test_ensure_historial_llamadas_table($pdo);
|
||||
$assessors = cc_test_fetch_assessors($pdo);
|
||||
$selectedAssessorFilter = cc_test_resolve_callcenter_assessor_filter($assessors, $_GET['assessor'] ?? null, true, true);
|
||||
$selectedAssessorFilter = cc_test_resolve_callcenter_assessor_filter($assessors, $_GET['assessor'] ?? null, false, false);
|
||||
$selectedAssessorFilterLabel = $selectedAssessorFilter !== ''
|
||||
? (string) ($assessors[$selectedAssessorFilter]['label'] ?? $selectedAssessorFilter)
|
||||
: '';
|
||||
$persistedAssessorParams = $selectedAssessorFilter !== ''
|
||||
? ['assessor' => $selectedAssessorFilter]
|
||||
: [];
|
||||
$persistedAssessorParams = [];
|
||||
if ($selectedAssessorFilterLabel !== '') {
|
||||
$pageTitle = 'Base de Datos Pedidos | Asignación · ' . $selectedAssessorFilterLabel;
|
||||
}
|
||||
@ -704,7 +702,7 @@ try {
|
||||
});
|
||||
|
||||
$orderedAssessorIds = [];
|
||||
foreach (cc_test_allowed_module_user_keys() as $assessorKey) {
|
||||
foreach (cc_test_ordered_assessor_keys($assessors) as $assessorKey) {
|
||||
if (isset($assessors[$assessorKey])) {
|
||||
$orderedAssessorIds[] = (int) ($assessors[$assessorKey]['id'] ?? 0);
|
||||
}
|
||||
@ -945,13 +943,7 @@ try {
|
||||
'footnote' => 'Seguimiento abierto = pedidos asignados hoy en POR LLAMAR, DEVOLVER LLAMADA, OBSERVADO y SE ENVIO NUMERO DE CUENTA que todavía no pasan a Promo Final.',
|
||||
]);
|
||||
|
||||
$orderedAssessorKeys = array_values(array_filter(cc_test_allowed_module_user_keys(), static function (string $key) use ($assessors): bool {
|
||||
return isset($assessors[$key]);
|
||||
}));
|
||||
|
||||
if (empty($orderedAssessorKeys)) {
|
||||
$orderedAssessorKeys = array_keys($assessors);
|
||||
}
|
||||
$orderedAssessorKeys = cc_test_ordered_assessor_keys($assessors);
|
||||
|
||||
$recommendedAssessorKey = null;
|
||||
$minPending = PHP_INT_MAX;
|
||||
@ -998,6 +990,28 @@ try {
|
||||
];
|
||||
}
|
||||
|
||||
usort($assessorSummaryCards, static function (array $a, array $b): int {
|
||||
$aPending = (int) ($a['pending'] ?? 0);
|
||||
$bPending = (int) ($b['pending'] ?? 0);
|
||||
|
||||
if ($aPending !== $bPending) {
|
||||
return $bPending <=> $aPending;
|
||||
}
|
||||
|
||||
return strcmp((string) ($a['label'] ?? ''), (string) ($b['label'] ?? ''));
|
||||
});
|
||||
|
||||
$activeAssessorSummaryCards = array_values(array_filter(
|
||||
$assessorSummaryCards,
|
||||
static fn (array $card): bool => (int) ($card['pending'] ?? 0) > 0
|
||||
));
|
||||
$inactiveAssessorSummaryCards = array_values(array_filter(
|
||||
$assessorSummaryCards,
|
||||
static fn (array $card): bool => (int) ($card['pending'] ?? 0) <= 0
|
||||
));
|
||||
$activeAssessorSummaryCount = count($activeAssessorSummaryCards);
|
||||
$inactiveAssessorSummaryCount = count($inactiveAssessorSummaryCards);
|
||||
|
||||
$assessorUiCatalog = [];
|
||||
foreach ($orderedAssessorKeys as $assessorKey) {
|
||||
if (!isset($assessors[$assessorKey])) {
|
||||
@ -1336,6 +1350,59 @@ require_once 'layout_header.php';
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.cc-callcenter-assessor-summary-card.is-inactive {
|
||||
opacity: .82;
|
||||
filter: saturate(.92);
|
||||
}
|
||||
|
||||
.cc-callcenter-assessor-add-card {
|
||||
border: 1px dashed rgba(13, 110, 253, .28) !important;
|
||||
background: linear-gradient(180deg, rgba(13, 110, 253, .06), rgba(13, 110, 253, .02));
|
||||
box-shadow: inset 0 0 0 1px rgba(13, 110, 253, .06);
|
||||
transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
|
||||
}
|
||||
|
||||
.cc-callcenter-assessor-add-card:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(13, 110, 253, .55) !important;
|
||||
box-shadow: 0 .75rem 1.5rem rgba(13, 110, 253, .08), inset 0 0 0 1px rgba(13, 110, 253, .08);
|
||||
}
|
||||
|
||||
.cc-callcenter-add-icon {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(13, 110, 253, .12);
|
||||
color: #0d6efd;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.cc-callcenter-empty-assessor-state {
|
||||
border: 1px dashed rgba(13, 110, 253, .2);
|
||||
background: linear-gradient(180deg, rgba(13, 110, 253, .05), rgba(13, 110, 253, .02));
|
||||
}
|
||||
|
||||
.cc-callcenter-details-summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
padding: .55rem .9rem;
|
||||
border-radius: 999px;
|
||||
background: #F8F9FA;
|
||||
border: 1px solid rgba(0, 0, 0, .08);
|
||||
color: #495057;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.cc-callcenter-details-summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cc-callcenter-action-btn {
|
||||
padding: .2rem .55rem;
|
||||
line-height: 1.1;
|
||||
@ -1343,6 +1410,31 @@ require_once 'layout_header.php';
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cc-reparto-card .card-body {
|
||||
padding: .85rem !important;
|
||||
}
|
||||
|
||||
.cc-reparto-chip {
|
||||
padding: .32rem .55rem;
|
||||
font-size: .72rem;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.cc-reparto-summary-note {
|
||||
font-size: .82rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.cc-reparto-state-grid {
|
||||
max-height: 18rem;
|
||||
overflow: auto;
|
||||
padding-right: .25rem;
|
||||
}
|
||||
|
||||
.cc-reparto-state-card {
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .36);
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.cc-callcenter-table tbody td {
|
||||
padding-top: .42rem;
|
||||
@ -1474,40 +1566,105 @@ require_once 'layout_header.php';
|
||||
<section class="mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
|
||||
<div>
|
||||
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-start gap-3 mb-3">
|
||||
<div class="flex-grow-1">
|
||||
<h2 class="h5 fw-bold mb-1">
|
||||
<i class="bi bi-activity text-primary"></i> Rendimiento por asesora
|
||||
</h2>
|
||||
<div class="text-muted small">Pendientes = <strong>POR LLAMAR</strong> / <strong>DEVOLVER LLAMADA</strong> / <strong>OBSERVADO</strong></div>
|
||||
<div class="text-muted small">Mostramos primero las asesoras con actividad para que la vista se vea más limpia. Si necesitas sumar otra, usa la tarjeta de alta al final.</div>
|
||||
<div class="d-flex flex-wrap gap-2 mt-2">
|
||||
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis border">Activas: <?php echo (int) $activeAssessorSummaryCount; ?></span>
|
||||
<span class="badge rounded-pill bg-light text-dark border">Sin actividad: <?php echo (int) $inactiveAssessorSummaryCount; ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 align-items-stretch justify-content-lg-end">
|
||||
<?php foreach ($assessorSummaryCards as $assessorCard): ?>
|
||||
<div class="border rounded-3 bg-light px-3 py-2 cc-callcenter-assessor-summary-card" style="min-width: 240px; <?php echo htmlspecialchars((string) $assessorCard['accent_style']); ?>">
|
||||
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||
<div class="fw-bold d-flex align-items-center gap-2">
|
||||
<span class="cc-callcenter-color-dot" aria-hidden="true"></span>
|
||||
<span><?php echo htmlspecialchars((string) $assessorCard['label']); ?></span>
|
||||
<form method="post" class="d-flex flex-column gap-2 align-items-stretch" style="min-width: 220px;">
|
||||
<input type="hidden" name="action" value="auto_assign_cupo">
|
||||
<button type="submit" class="btn btn-primary btn-sm w-100" <?php echo $tieneCupos ? '' : 'disabled'; ?>>
|
||||
Rellenar cupos
|
||||
</button>
|
||||
<div class="small text-muted">Asigna pedidos sin asignar hasta completar el cupo.</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php if ($activeAssessorSummaryCount > 0): ?>
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3">
|
||||
<?php foreach ($activeAssessorSummaryCards as $assessorCard): ?>
|
||||
<div class="col">
|
||||
<div class="card border-0 shadow-sm h-100 cc-callcenter-assessor-summary-card" style="<?php echo htmlspecialchars((string) $assessorCard['accent_style']); ?>">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||
<div class="fw-bold d-flex align-items-center gap-2">
|
||||
<span class="cc-callcenter-color-dot" aria-hidden="true"></span>
|
||||
<span><?php echo htmlspecialchars((string) $assessorCard['label']); ?></span>
|
||||
</div>
|
||||
<span class="badge border cc-callcenter-assessor-badge">Pendientes: <?php echo (int) $assessorCard['pending']; ?></span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
|
||||
<div class="h3 fw-bold mb-0"><?php echo (int) $assessorCard['remaining']; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge border cc-callcenter-assessor-badge">Pendientes: <?php echo (int) $assessorCard['pending']; ?></span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
|
||||
<div class="h3 fw-bold mb-0"><?php echo (int) $assessorCard['remaining']; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<form method="post" class="d-flex flex-column gap-2" style="min-width: 220px;">
|
||||
<input type="hidden" name="action" value="auto_assign_cupo">
|
||||
<button type="submit" class="btn btn-primary btn-sm w-100" <?php echo $tieneCupos ? '' : 'disabled'; ?>>
|
||||
Rellenar cupos
|
||||
</button>
|
||||
<div class="small text-muted">Asigna pedidos sin asignar hasta completar el cupo.</div>
|
||||
</form>
|
||||
<div class="col">
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('manage_users.php', ['role' => 'Asesor']) . '#crear-asesora'); ?>" class="card border-0 shadow-sm h-100 text-decoration-none cc-callcenter-assessor-add-card">
|
||||
<div class="card-body d-flex flex-column justify-content-between h-100">
|
||||
<div>
|
||||
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis border">Opcional</span>
|
||||
<h3 class="h6 fw-bold mt-3 mb-1">Agregar más asesoras</h3>
|
||||
<p class="text-muted small mb-0">Abre el alta de usuarios cuando quieras sumar otra persona al equipo.</p>
|
||||
</div>
|
||||
<div class="d-inline-flex align-items-center gap-2 mt-3 fw-semibold">
|
||||
<span class="cc-callcenter-add-icon" aria-hidden="true"><i class="bi bi-person-plus"></i></span>
|
||||
<span>Abrir alta</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="cc-callcenter-empty-assessor-state rounded-4 p-4">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3">
|
||||
<div>
|
||||
<div class="fw-semibold mb-1">No hay asesoras con actividad en este momento</div>
|
||||
<div class="text-muted small mb-0">Cuando una asesora reciba pedidos, aparecerá aquí. Mientras tanto puedes crear una nueva o revisar las existentes sin actividad.</div>
|
||||
</div>
|
||||
<a href="<?php echo htmlspecialchars(cc_test_build_url('manage_users.php', ['role' => 'Asesor']) . '#crear-asesora'); ?>" class="btn btn-outline-primary">
|
||||
<i class="bi bi-person-plus me-1"></i> Agregar asesora
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($inactiveAssessorSummaryCount > 0): ?>
|
||||
<details class="mt-3">
|
||||
<summary class="cc-callcenter-details-summary">Ver <?php echo (int) $inactiveAssessorSummaryCount; ?> asesora<?php echo $inactiveAssessorSummaryCount === 1 ? '' : 's'; ?> sin actividad</summary>
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-3 mt-2">
|
||||
<?php foreach ($inactiveAssessorSummaryCards as $assessorCard): ?>
|
||||
<div class="col">
|
||||
<div class="card border-0 shadow-sm h-100 cc-callcenter-assessor-summary-card is-inactive" style="<?php echo htmlspecialchars((string) $assessorCard['accent_style']); ?>">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center gap-2">
|
||||
<div class="fw-bold d-flex align-items-center gap-2">
|
||||
<span class="cc-callcenter-color-dot" aria-hidden="true"></span>
|
||||
<span><?php echo htmlspecialchars((string) $assessorCard['label']); ?></span>
|
||||
</div>
|
||||
<span class="badge border cc-callcenter-assessor-badge">Pendientes: <?php echo (int) $assessorCard['pending']; ?></span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="small text-muted">Cupo restante (<?php echo (int) $cupoObjetivoPorAsesora; ?>)</div>
|
||||
<div class="h3 fw-bold mb-0"><?php echo (int) $assessorCard['remaining']; ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -1525,53 +1682,55 @@ require_once 'layout_header.php';
|
||||
? cc_test_format_date((string) $repartoSettings['rotation_date'], 'Sin fecha')
|
||||
: 'Sin ciclo aún';
|
||||
?>
|
||||
<section class="card border-0 shadow-sm mb-4" id="repartoIntercalado">
|
||||
<section class="card border-0 shadow-sm mb-3 cc-reparto-card" id="repartoIntercalado">
|
||||
<div class="card-body">
|
||||
<div class="row g-3 align-items-start">
|
||||
<div class="col-12 col-xl-7">
|
||||
<div class="d-flex flex-wrap align-items-center gap-2 mb-2">
|
||||
<span class="badge rounded-pill <?php echo htmlspecialchars($repartoModeClass); ?>">Reparto <?php echo htmlspecialchars($repartoModeLabel); ?></span>
|
||||
<div class="row g-2 align-items-start">
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="d-flex flex-wrap align-items-center gap-1 mb-2">
|
||||
<span class="badge rounded-pill cc-reparto-chip <?php echo htmlspecialchars($repartoModeClass); ?>">Reparto <?php echo htmlspecialchars($repartoModeLabel); ?></span>
|
||||
<?php if ($repartoNextLabel !== ''): ?>
|
||||
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis border">Siguiente: <?php echo htmlspecialchars($repartoNextLabel); ?></span>
|
||||
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis border cc-reparto-chip">Siguiente: <?php echo htmlspecialchars($repartoNextLabel); ?></span>
|
||||
<?php else: ?>
|
||||
<span class="badge rounded-pill bg-warning-subtle text-warning-emphasis border">Sin asesoras disponibles</span>
|
||||
<span class="badge rounded-pill bg-warning-subtle text-warning-emphasis border cc-reparto-chip">Sin asesoras disponibles</span>
|
||||
<?php endif; ?>
|
||||
<span class="badge rounded-pill bg-light text-dark border">Sin asignar: <?php echo (int) $repartoUnassignedCount; ?></span>
|
||||
<span class="badge rounded-pill bg-light text-dark border">Elegibles para reparto: <?php echo (int) $repartoEligibleUnassignedCount; ?></span>
|
||||
<span class="badge rounded-pill bg-light text-dark border cc-reparto-chip">Sin asignar: <?php echo (int) $repartoUnassignedCount; ?></span>
|
||||
<span class="badge rounded-pill bg-light text-dark border cc-reparto-chip">Elegibles: <?php echo (int) $repartoEligibleUnassignedCount; ?></span>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-center gap-2 mb-2">
|
||||
<h2 class="h6 fw-bold mb-0">Reparto de pedidos pendientes</h2>
|
||||
<span class="text-muted small cc-reparto-summary-note">Vista compacta para revisar y cambiar el reparto sin ocupar tanto alto.</span>
|
||||
</div>
|
||||
<h2 class="h5 fw-bold mb-2">Reparto de pedidos pendientes</h2>
|
||||
<p class="text-muted small mb-3">El historial anterior se mantiene visible. El reparto manual trabaja sobre los pedidos actuales sin ocultar los anteriores.</p>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="badge rounded-pill bg-light text-dark border">Asesoras totales: <?php echo (int) $repartoTotalCount; ?></span>
|
||||
<span class="badge rounded-pill bg-light text-dark border">Disponibles: <?php echo (int) $repartoAvailableCount; ?></span>
|
||||
<span class="badge rounded-pill bg-light text-dark border">Ciclo: <?php echo htmlspecialchars($repartoRotationDateLabel); ?></span>
|
||||
<span class="badge rounded-pill bg-light text-dark border cc-reparto-chip">Asesoras totales: <?php echo (int) $repartoTotalCount; ?></span>
|
||||
<span class="badge rounded-pill bg-light text-dark border cc-reparto-chip">Disponibles: <?php echo (int) $repartoAvailableCount; ?></span>
|
||||
<span class="badge rounded-pill bg-light text-dark border cc-reparto-chip">Ciclo: <?php echo htmlspecialchars($repartoRotationDateLabel); ?></span>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||
<div class="d-flex flex-wrap gap-2 mt-2">
|
||||
<?php foreach (($repartoPreview['sequence'] ?? []) as $item): ?>
|
||||
<span class="badge rounded-pill <?php echo htmlspecialchars((string) ($item['badge_class'] ?? 'bg-light text-dark border')); ?>">
|
||||
<span class="badge rounded-pill <?php echo htmlspecialchars((string) ($item['badge_class'] ?? 'bg-light text-dark border')); ?> cc-reparto-chip">
|
||||
<?php echo htmlspecialchars((string) ($item['label'] ?? '')); ?> · <?php echo htmlspecialchars((string) ($item['state_label'] ?? '')); ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-5">
|
||||
<form method="post" class="border rounded-4 p-3 bg-light h-100">
|
||||
<div class="col-12 col-xl-6">
|
||||
<form method="post" class="border rounded-4 p-2 bg-light h-100">
|
||||
<input type="hidden" name="action" value="save_reparto_config">
|
||||
<input type="hidden" name="store" value="<?php echo htmlspecialchars($storeKey); ?>">
|
||||
<input type="hidden" name="view" value="<?php echo htmlspecialchars($view); ?>">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small text-uppercase text-muted fw-semibold mb-1" for="repartoMode">Modo de reparto</label>
|
||||
<select name="reparto_mode" id="repartoMode" class="form-select">
|
||||
<div class="mb-2">
|
||||
<label class="form-label small text-muted fw-semibold mb-1" for="repartoMode">Modo de reparto</label>
|
||||
<select name="reparto_mode" id="repartoMode" class="form-select form-select-sm">
|
||||
<option value="manual"<?php echo ($repartoPreview['mode'] ?? 'manual') === 'manual' ? ' selected' : ''; ?>>Manual</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="row row-cols-1 row-cols-md-2 g-2 cc-reparto-state-grid">
|
||||
<?php foreach (($repartoPreview['sequence'] ?? []) as $item): ?>
|
||||
<div class="col-12">
|
||||
<div class="bg-white border rounded-4 p-2">
|
||||
<div class="col">
|
||||
<div class="bg-white border rounded-3 p-2 h-100 cc-reparto-state-card">
|
||||
<div class="d-flex justify-content-between align-items-center gap-2 mb-2">
|
||||
<div class="fw-semibold"><?php echo htmlspecialchars((string) ($item['label'] ?? '')); ?></div>
|
||||
<span class="badge rounded-pill <?php echo htmlspecialchars((string) ($item['badge_class'] ?? 'bg-light text-dark border')); ?>"><?php echo htmlspecialchars((string) ($item['state_label'] ?? '')); ?></span>
|
||||
<div class="fw-semibold small"><?php echo htmlspecialchars((string) ($item['label'] ?? '')); ?></div>
|
||||
<span class="badge rounded-pill <?php echo htmlspecialchars((string) ($item['badge_class'] ?? 'bg-light text-dark border')); ?> cc-reparto-chip"><?php echo htmlspecialchars((string) ($item['state_label'] ?? '')); ?></span>
|
||||
</div>
|
||||
<select name="assessor_state[<?php echo htmlspecialchars((string) ($item['key'] ?? '')); ?>]" class="form-select form-select-sm">
|
||||
<option value="disponible"<?php echo ($item['state'] ?? 'disponible') === 'disponible' ? ' selected' : ''; ?>>Disponible</option>
|
||||
@ -1582,15 +1741,15 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||
<button type="submit" class="btn btn-primary">Guardar configuración</button>
|
||||
<div class="d-flex flex-wrap gap-2 mt-2">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Guardar configuración</button>
|
||||
</div>
|
||||
</form>
|
||||
<form method="post" class="mt-3">
|
||||
<form method="post" class="mt-2">
|
||||
<input type="hidden" name="action" value="run_daily_reparto">
|
||||
<input type="hidden" name="store" value="<?php echo htmlspecialchars($storeKey); ?>">
|
||||
<input type="hidden" name="view" value="<?php echo htmlspecialchars($view); ?>">
|
||||
<button type="submit" class="btn btn-outline-primary w-100">Distribuir pedidos pendientes</button>
|
||||
<button type="submit" class="btn btn-outline-primary btn-sm w-100">Distribuir pedidos pendientes</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -1756,7 +1915,7 @@ require_once 'layout_header.php';
|
||||
: (string) ($assessors[$assignedAssessorKey]['label'] ?? ($assignedAssessorKey ?? 'Asignado'));
|
||||
?>
|
||||
|
||||
<form method="post" class="border rounded-3 p-1 text-start cc-callcenter-assign-form<?php echo $assignFormIsUnassigned ? ' is-unassigned' : ''; ?>" style="<?php echo htmlspecialchars($assignFormStyle); ?>">
|
||||
<form method="post" class="border rounded-3 p-1 text-start cc-callcenter-assign-form<?php echo $assignFormIsUnassigned ? ' is-unassigned' : ''; ?>" style="<?php echo htmlspecialchars($assignFormStyle); ?>" autocomplete="off">
|
||||
<input type="hidden" name="action" value="assign_assessor">
|
||||
<input type="hidden" name="source_key" value="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||
|
||||
@ -1769,7 +1928,8 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
|
||||
<div class="cc-callcenter-assign-row">
|
||||
<select name="target_assessor" class="form-select form-select-sm js-assessor-select">
|
||||
<input type="hidden" name="target_assessor" class="js-assessor-hidden" value="<?php echo htmlspecialchars((string) $selectedAssessorKeyForSelect); ?>">
|
||||
<select class="form-select form-select-sm js-assessor-select" data-server-assessor="<?php echo htmlspecialchars((string) $selectedAssessorKeyForSelect); ?>" autocomplete="off" data-lpignore="true" data-1p-ignore="true" data-bwignore="true">
|
||||
<option value=""<?php echo ($selectedAssessorKeyForSelect === '') ? ' selected' : ''; ?>>Sin asignar</option>
|
||||
|
||||
<?php foreach ($assessors as $assessorKey => $assessor): ?>
|
||||
@ -3156,9 +3316,52 @@ function syncAssessorFormAccent(form) {
|
||||
}
|
||||
}
|
||||
|
||||
function restoreServerAssessorSelection(select) {
|
||||
if (!select) {
|
||||
return;
|
||||
}
|
||||
|
||||
const form = select.closest('form');
|
||||
const hiddenInput = form ? form.querySelector('.js-assessor-hidden') : null;
|
||||
const serverAssessor = typeof select.dataset.serverAssessor === 'string'
|
||||
? (select.dataset.serverAssessor || '')
|
||||
: (hiddenInput ? (hiddenInput.value || '') : '');
|
||||
|
||||
if ((select.value || '') !== serverAssessor) {
|
||||
select.value = serverAssessor;
|
||||
}
|
||||
|
||||
if (hiddenInput && (hiddenInput.value || '') !== (select.value || '')) {
|
||||
hiddenInput.value = select.value || '';
|
||||
}
|
||||
}
|
||||
|
||||
function syncAssessorHiddenInput(form) {
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
|
||||
const select = form.querySelector('.js-assessor-select');
|
||||
const hiddenInput = form.querySelector('.js-assessor-hidden');
|
||||
if (!select || !hiddenInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
hiddenInput.value = select.value || '';
|
||||
}
|
||||
|
||||
function syncAllAssessorFormsFromServer() {
|
||||
document.querySelectorAll('.cc-callcenter-assign-form').forEach(function (form) {
|
||||
const select = form.querySelector('.js-assessor-select');
|
||||
restoreServerAssessorSelection(select);
|
||||
syncAssessorFormAccent(form);
|
||||
});
|
||||
}
|
||||
|
||||
function openAssessorColorModal(trigger) {
|
||||
const form = trigger ? trigger.closest('form') : null;
|
||||
const select = form ? form.querySelector('.js-assessor-select') : null;
|
||||
restoreServerAssessorSelection(select);
|
||||
const assessorKey = select ? (select.value || '') : '';
|
||||
const config = assessorKey && ccAssessorCatalog[assessorKey] ? ccAssessorCatalog[assessorKey] : null;
|
||||
|
||||
@ -3188,15 +3391,21 @@ function openAssessorColorModal(trigger) {
|
||||
|
||||
|
||||
document.querySelectorAll('.cc-callcenter-assign-form').forEach(function (form) {
|
||||
const select = form.querySelector('.js-assessor-select');
|
||||
restoreServerAssessorSelection(select);
|
||||
syncAssessorFormAccent(form);
|
||||
|
||||
const select = form.querySelector('.js-assessor-select');
|
||||
if (select) {
|
||||
select.addEventListener('change', function () {
|
||||
syncAssessorHiddenInput(form);
|
||||
syncAssessorFormAccent(form);
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function () {
|
||||
syncAssessorHiddenInput(form);
|
||||
});
|
||||
|
||||
const colorButton = form.querySelector('.js-assessor-color-trigger');
|
||||
if (colorButton) {
|
||||
colorButton.addEventListener('click', function () {
|
||||
@ -3205,6 +3414,17 @@ document.querySelectorAll('.cc-callcenter-assign-form').forEach(function (form)
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('pageshow', function () {
|
||||
syncAllAssessorFormsFromServer();
|
||||
});
|
||||
window.addEventListener('load', function () {
|
||||
syncAllAssessorFormsFromServer();
|
||||
});
|
||||
window.requestAnimationFrame(syncAllAssessorFormsFromServer);
|
||||
window.setTimeout(syncAllAssessorFormsFromServer, 0);
|
||||
window.setTimeout(syncAllAssessorFormsFromServer, 120);
|
||||
window.setTimeout(syncAllAssessorFormsFromServer, 600);
|
||||
|
||||
if (ccAssessorColorInput) {
|
||||
ccAssessorColorInput.addEventListener('input', function () {
|
||||
const label = ccAssessorColorAssessorLabel ? (ccAssessorColorAssessorLabel.value || 'Asesora') : 'Asesora';
|
||||
|
||||
@ -68,11 +68,19 @@ function cc_test_hex_to_rgb_triplet(string $hex): ?array
|
||||
|
||||
function cc_test_assessor_default_color_hex(string $assessorKey): string
|
||||
{
|
||||
return match (cc_test_normalize_user_key($assessorKey)) {
|
||||
$normalizedKey = cc_test_normalize_user_key($assessorKey);
|
||||
if ($normalizedKey === '') {
|
||||
return '#6C757D';
|
||||
}
|
||||
|
||||
$palette = ['#0D6EFD', '#198754', '#FD7E14', '#DC3545', '#20C997', '#FFC107', '#D63384'];
|
||||
$paletteIndex = abs((int) crc32($normalizedKey)) % count($palette);
|
||||
|
||||
return match ($normalizedKey) {
|
||||
'KARINA' => '#0D6EFD',
|
||||
'ESTEFANYA' => '#198754',
|
||||
'CARMEN' => '#FF00FF',
|
||||
default => '#6C757D',
|
||||
default => $palette[$paletteIndex],
|
||||
};
|
||||
}
|
||||
|
||||
@ -772,8 +780,9 @@ function cc_test_apply_daily_reparto(PDO $pdo, string $storeKey, array $orders,
|
||||
|
||||
function cc_test_fetch_assessors(PDO $pdo, array $allowedNames = []): array
|
||||
{
|
||||
if (empty($allowedNames)) {
|
||||
$allowedNames = cc_test_default_assessor_keys();
|
||||
$allowedLookup = [];
|
||||
if (!empty($allowedNames)) {
|
||||
$allowedLookup = array_fill_keys(array_map('cc_test_normalize_user_key', $allowedNames), true);
|
||||
}
|
||||
|
||||
$hasColorColumn = cc_test_column_exists($pdo, 'users', 'color_hex');
|
||||
@ -781,7 +790,6 @@ function cc_test_fetch_assessors(PDO $pdo, array $allowedNames = []): array
|
||||
$stmt = $pdo->prepare("SELECT id, username, nombre_asesor{$selectColorColumn} FROM users WHERE role = 'Asesor'");
|
||||
$stmt->execute();
|
||||
|
||||
$allowedLookup = array_fill_keys(array_map('strtoupper', $allowedNames), true);
|
||||
$byKey = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$key = trim(mb_strtoupper((string) ($row['nombre_asesor'] ?: $row['username'] ?: '')));
|
||||
@ -893,6 +901,10 @@ function cc_test_is_allowed_module_user(?string $role = null, ?string $username
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($role === 'Asesor') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$allowedLookup = array_fill_keys(cc_test_allowed_module_user_keys(), true);
|
||||
foreach ([$username, $nombreAsesor] as $candidate) {
|
||||
$key = cc_test_normalize_user_key($candidate);
|
||||
@ -1329,12 +1341,7 @@ function cc_test_build_performance_dashboard(array $orders, array $assessors, in
|
||||
$trendLabels[] = $day->format('d/m');
|
||||
}
|
||||
|
||||
$orderedAssessorKeys = array_values(array_filter(cc_test_allowed_module_user_keys(), static function (string $key) use ($assessors): bool {
|
||||
return isset($assessors[$key]);
|
||||
}));
|
||||
if (empty($orderedAssessorKeys)) {
|
||||
$orderedAssessorKeys = array_keys($assessors);
|
||||
}
|
||||
$orderedAssessorKeys = cc_test_ordered_assessor_keys($assessors);
|
||||
|
||||
$bucketStats = [];
|
||||
foreach ($orderedAssessorKeys as $assessorKey) {
|
||||
|
||||
@ -14,6 +14,12 @@ if (isset($_SESSION['message'])) {
|
||||
unset($_SESSION['message']);
|
||||
}
|
||||
|
||||
$defaultRole = trim((string) ($_GET['role'] ?? ''));
|
||||
$allowedDefaultRoles = ['Administrador', 'Asesor', 'Control Logistico', 'Soporte Logistico', 'Verificador de Pagos', 'Logistica'];
|
||||
if ($defaultRole !== '' && !in_array($defaultRole, $allowedDefaultRoles, true)) {
|
||||
$defaultRole = '';
|
||||
}
|
||||
|
||||
// Handle form submission for creating a new user
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_user'])) {
|
||||
$username = trim($_POST['username']);
|
||||
@ -124,7 +130,7 @@ include 'layout_header.php';
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class="card">
|
||||
<div class="card" id="crear-asesora">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Crear Nuevo Usuario</h3>
|
||||
</div>
|
||||
@ -150,12 +156,12 @@ include 'layout_header.php';
|
||||
<label for="role">Rol</label>
|
||||
<select class="form-control" id="role" name="role" required>
|
||||
<option value="">Seleccione un rol</option>
|
||||
<option value="Administrador">Administrador</option>
|
||||
<option value="Asesor">Asesor</option>
|
||||
<option value="Control Logistico">Control Logístico</option>
|
||||
<option value="Soporte Logistico">Soporte Logístico</option>
|
||||
<option value="Verificador de Pagos">Verificador de Pagos</option>
|
||||
<option value="Logistica">Logistica</option>
|
||||
<option value="Administrador"<?php echo $defaultRole === 'Administrador' ? ' selected' : ''; ?>>Administrador</option>
|
||||
<option value="Asesor"<?php echo $defaultRole === 'Asesor' ? ' selected' : ''; ?>>Asesor</option>
|
||||
<option value="Control Logistico"<?php echo $defaultRole === 'Control Logistico' ? ' selected' : ''; ?>>Control Logístico</option>
|
||||
<option value="Soporte Logistico"<?php echo $defaultRole === 'Soporte Logistico' ? ' selected' : ''; ?>>Soporte Logístico</option>
|
||||
<option value="Verificador de Pagos"<?php echo $defaultRole === 'Verificador de Pagos' ? ' selected' : ''; ?>>Verificador de Pagos</option>
|
||||
<option value="Logistica"<?php echo $defaultRole === 'Logistica' ? ' selected' : ''; ?>>Logistica</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" name="create_user" class="btn btn-primary w-100">Crear Usuario</button>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user