Mejora Transacción y Historial: estado por fila, envío individual y filtros avanzados
- Transacción: tabla con estado Enviado/Pendiente/Error por IDRECEPCION, botón enviar fila a fila o masivo, modal con JSON + respuesta TNS por registro
- Historial: filtros por fecha, tipo, IDRECEPCION/contrato; paginación 50 por página; modal con 3 tabs (JSON enviado / Respuesta TNS / Mensaje)
- DB: migración automática para agregar columnas idrecepcion, contrato, mensaje_tns a tabla envios
- Logs route: nuevo endpoint /logs/detalle/{id} para cargar detalle completo vía JS
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ef71a1db56
commit
7699303a53
+236
-95
@@ -2,131 +2,272 @@
|
||||
{% block title %}Historial{% endblock %}
|
||||
{% block header %}Historial de Envíos{% endblock %}
|
||||
{% block content %}
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<form class="flex flex-wrap items-end gap-4">
|
||||
|
||||
<!-- Stats rápidos -->
|
||||
{% if stats %}
|
||||
<div class="flex flex-wrap gap-3 mb-5">
|
||||
{% for s in stats %}
|
||||
<div class="bg-white rounded-lg border border-gray-200 px-4 py-2.5 shadow-sm flex items-center gap-3">
|
||||
<span class="px-2 py-0.5 rounded text-xs font-medium
|
||||
{% if s.tipo == 'transaccion' %}bg-purple-100 text-purple-700
|
||||
{% else %}bg-blue-100 text-blue-700{% endif %}">{{ s.tipo }}</span>
|
||||
<span class="{% if s.status == 'success' %}text-green-600{% else %}text-red-600{% endif %} font-bold text-sm">
|
||||
{{ s.cnt }}
|
||||
</span>
|
||||
<span class="text-gray-400 text-xs">{{ 'exitosos' if s.status == 'success' else 'errores' }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="bg-white rounded-lg border border-gray-200 px-4 py-2.5 shadow-sm flex items-center gap-2 text-gray-500 text-sm">
|
||||
<i class="fas fa-database text-gray-400 text-xs"></i> {{ total }} total
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Filtros -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-5">
|
||||
<form method="get" action="/logs" class="p-4">
|
||||
<input type="hidden" name="offset" value="0">
|
||||
<div class="flex flex-wrap items-end gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Tipo</label>
|
||||
<select name="tipo" class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm">
|
||||
<option value="">Todos</option>
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">Tipo</label>
|
||||
<select name="tipo" class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm min-w-[130px]">
|
||||
<option value="">Todos los tipos</option>
|
||||
<option value="terceros" {{ 'selected' if filtro_tipo == 'terceros' }}>Terceros</option>
|
||||
<option value="transaccion" {{ 'selected' if filtro_tipo == 'transaccion' }}>Transacción</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Estado</label>
|
||||
<select name="status" class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm">
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">Estado</label>
|
||||
<select name="status" class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm min-w-[120px]">
|
||||
<option value="">Todos</option>
|
||||
<option value="success" {{ 'selected' if filtro_status == 'success' }}>Exitoso</option>
|
||||
<option value="error" {{ 'selected' if filtro_status == 'error' }}>Error</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Factura</label>
|
||||
<input type="text" name="factura" value="{{ filtro_factura }}"
|
||||
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm" placeholder="Buscar factura...">
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">Desde</label>
|
||||
<input type="date" name="fecha_desde" value="{{ filtro_fecha_desde }}"
|
||||
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">Hasta</label>
|
||||
<input type="date" name="fecha_hasta" value="{{ filtro_fecha_hasta }}"
|
||||
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">Factura</label>
|
||||
<input type="text" name="factura" value="{{ filtro_factura }}" placeholder="Nro. factura"
|
||||
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm w-28">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">IDRECEP / Contrato</label>
|
||||
<input type="text" name="paciente" value="{{ filtro_paciente }}" placeholder="ID o contrato"
|
||||
class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm w-36">
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button type="submit"
|
||||
class="px-4 py-1.5 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">
|
||||
<i class="fas fa-search mr-1"></i> Filtrar
|
||||
</button>
|
||||
<a href="/logs" class="px-3 py-1.5 bg-gray-100 text-gray-600 rounded-lg text-sm hover:bg-gray-200" title="Limpiar filtros">
|
||||
<i class="fas fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-1.5 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700">
|
||||
<i class="fas fa-search mr-1"></i> Filtrar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
{% if envios %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="text-left text-gray-500 border-b border-gray-200">
|
||||
<th class="pb-3 font-medium">ID</th>
|
||||
<th class="pb-3 font-medium">Tipo</th>
|
||||
<th class="pb-3 font-medium">Factura</th>
|
||||
<th class="pb-3 font-medium">Estado</th>
|
||||
<th class="pb-3 font-medium">Pacientes</th>
|
||||
<th class="pb-3 font-medium">Servicios</th>
|
||||
<th class="pb-3 font-medium">Respuesta API</th>
|
||||
<th class="pb-3 font-medium">Fecha</th>
|
||||
<th class="pb-3 font-medium">Usuario</th>
|
||||
<th class="pb-3 font-medium"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for e in envios %}
|
||||
<tr class="border-b border-gray-50 hover:bg-gray-50">
|
||||
<td class="py-3 text-gray-600">{{ e.id }}</td>
|
||||
<td class="py-3">
|
||||
<span class="px-2 py-1 rounded text-xs font-medium {% if e.tipo == 'terceros' %}bg-blue-100 text-blue-700{% else %}bg-purple-100 text-purple-700{% endif %}">
|
||||
{{ e.tipo }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-3 text-gray-600 font-medium">{{ e.factura or '-' }}</td>
|
||||
<td class="py-3">
|
||||
<span class="px-2 py-1 rounded text-xs font-medium {% if e.status == 'success' %}bg-green-100 text-green-700{% else %}bg-red-100 text-red-700{% endif %}">
|
||||
{{ 'Exitoso' if e.status == 'success' else 'Error' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-3 text-gray-600">{{ e.pacientes_count or 0 }}</td>
|
||||
<td class="py-3 text-gray-600">{{ e.servicios_count or 0 }}</td>
|
||||
<td class="py-3 text-gray-500 text-xs max-w-xs truncate">{{ e.respuesta_api[:80] if e.respuesta_api else '-' }}</td>
|
||||
<td class="py-3 text-gray-500 text-xs">{{ e.created_at[:19] }}</td>
|
||||
<td class="py-3 text-gray-500">{{ e.username }}</td>
|
||||
<td class="py-3">
|
||||
<button onclick='showDetail({{ e.id|tojson }}, {{ e.json_enviado|tojson if e.json_enviado else "null" }})'
|
||||
class="text-blue-600 hover:text-blue-800">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-12 text-gray-400">
|
||||
<i class="fas fa-inbox text-5xl mb-4 block"></i>
|
||||
<p>No hay registros con los filtros seleccionados</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Modal JSON -->
|
||||
<div id="json-modal" class="fixed inset-0 z-50 hidden">
|
||||
<div class="absolute inset-0 bg-black/50" onclick="closeModal()"></div>
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-2xl bg-white rounded-xl shadow-2xl max-h-[80vh] overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
|
||||
<h3 class="font-semibold text-gray-800"><i class="fas fa-code mr-2 text-blue-500"></i>JSON Enviado</h3>
|
||||
<button onclick="closeModal()" class="text-gray-400 hover:text-gray-600"><i class="fas fa-times"></i></button>
|
||||
<!-- Tabla -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
|
||||
<div class="px-6 py-3 border-b border-gray-100 flex items-center justify-between">
|
||||
<span class="text-xs text-gray-500">
|
||||
{% if total %}
|
||||
Mostrando {{ offset + 1 }}–{{ [offset + page_size, total]|min }} de {{ total }}
|
||||
{% else %}Sin resultados{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if envios %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-xs">
|
||||
<thead>
|
||||
<tr class="text-left text-gray-400 border-b border-gray-200 bg-gray-50/60">
|
||||
<th class="px-4 py-3 font-medium">#</th>
|
||||
<th class="px-2 py-3 font-medium">Tipo</th>
|
||||
<th class="px-2 py-3 font-medium">Factura</th>
|
||||
<th class="px-2 py-3 font-medium">IDRECEP.</th>
|
||||
<th class="px-2 py-3 font-medium">Contrato</th>
|
||||
<th class="px-2 py-3 font-medium">Estado</th>
|
||||
<th class="px-2 py-3 font-medium">Mensaje TNS</th>
|
||||
<th class="px-2 py-3 font-medium">Fecha</th>
|
||||
<th class="px-2 py-3 font-medium">Usuario</th>
|
||||
<th class="px-2 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for e in envios %}
|
||||
<tr class="border-b border-gray-50 hover:bg-gray-50 transition-colors
|
||||
{% if e.status == 'success' %}bg-green-50/25{% elif e.status == 'error' %}bg-red-50/25{% endif %}">
|
||||
<td class="px-4 py-2.5 text-gray-400 font-mono">{{ e.id }}</td>
|
||||
<td class="px-2 py-2.5">
|
||||
<span class="px-2 py-0.5 rounded text-xs font-medium
|
||||
{% if e.tipo == 'terceros' %}bg-blue-100 text-blue-700
|
||||
{% else %}bg-purple-100 text-purple-700{% endif %}">
|
||||
{{ e.tipo }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-2.5 text-gray-600 font-medium">{{ e.factura or '-' }}</td>
|
||||
<td class="px-2 py-2.5 text-gray-500 font-mono">{{ e.idrecepcion or '-' }}</td>
|
||||
<td class="px-2 py-2.5 text-gray-500">{{ e.contrato or '-' }}</td>
|
||||
<td class="px-2 py-2.5">
|
||||
<span class="px-2 py-0.5 rounded text-xs font-medium
|
||||
{% if e.status == 'success' %}bg-green-100 text-green-700
|
||||
{% else %}bg-red-100 text-red-700{% endif %}">
|
||||
{{ '✓ OK' if e.status == 'success' else '✗ Error' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-2.5 text-gray-500">
|
||||
{% if e.mensaje_tns %}
|
||||
<span class="block max-w-[180px] truncate" title="{{ e.mensaje_tns }}">
|
||||
{{ e.mensaje_tns[:55] }}{% if e.mensaje_tns|length > 55 %}…{% endif %}
|
||||
</span>
|
||||
{% else %}<span class="text-gray-300">—</span>{% endif %}
|
||||
</td>
|
||||
<td class="px-2 py-2.5 text-gray-400 whitespace-nowrap">{{ e.created_at[:16] if e.created_at else '-' }}</td>
|
||||
<td class="px-2 py-2.5 text-gray-400">{{ e.username or '-' }}</td>
|
||||
<td class="px-2 py-2.5">
|
||||
<button onclick="verDetalle({{ e.id }})"
|
||||
class="px-2 py-1 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded" title="Ver detalle completo">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Paginación -->
|
||||
<div class="px-6 py-4 border-t border-gray-100 flex items-center justify-between">
|
||||
<div class="flex gap-2">
|
||||
{% if offset > 0 %}
|
||||
<a href="/logs?tipo={{ filtro_tipo }}&status={{ filtro_status }}&factura={{ filtro_factura }}&paciente={{ filtro_paciente }}&fecha_desde={{ filtro_fecha_desde }}&fecha_hasta={{ filtro_fecha_hasta }}&offset={{ [0, offset - page_size]|max }}"
|
||||
class="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded-lg text-sm">
|
||||
<i class="fas fa-chevron-left mr-1"></i> Anterior
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if has_more %}
|
||||
<a href="/logs?tipo={{ filtro_tipo }}&status={{ filtro_status }}&factura={{ filtro_factura }}&paciente={{ filtro_paciente }}&fecha_desde={{ filtro_fecha_desde }}&fecha_hasta={{ filtro_fecha_hasta }}&offset={{ offset + page_size }}"
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm">
|
||||
Siguiente <i class="fas fa-chevron-right ml-1"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="p-6 overflow-y-auto max-h-[calc(80vh-80px)]">
|
||||
<pre id="modal-json" class="text-xs font-mono bg-gray-50 rounded-lg p-4 overflow-x-auto"></pre>
|
||||
<span class="text-xs text-gray-400">Pág. {{ (offset // page_size) + 1 }}</span>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="text-center py-16 text-gray-400">
|
||||
<i class="fas fa-inbox text-5xl mb-4 block opacity-30"></i>
|
||||
<p class="text-sm">Sin registros con los filtros seleccionados</p>
|
||||
<a href="/logs" class="mt-3 inline-block text-blue-600 hover:underline text-sm">Quitar filtros</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Modal detalle -->
|
||||
<div id="modal-detalle" class="fixed inset-0 z-50 hidden">
|
||||
<div class="absolute inset-0 bg-black/60" onclick="closeModal()"></div>
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-3xl bg-white rounded-xl shadow-2xl max-h-[88vh] overflow-hidden flex flex-col">
|
||||
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center shrink-0">
|
||||
<h3 id="modal-titulo" class="font-semibold text-gray-800 text-sm"></h3>
|
||||
<button onclick="closeModal()" class="text-gray-400 hover:text-gray-600 text-xl leading-none">×</button>
|
||||
</div>
|
||||
<div id="modal-info" class="px-6 py-2.5 bg-gray-50 border-b border-gray-200 shrink-0 text-xs text-gray-500 flex flex-wrap gap-4"></div>
|
||||
<div class="flex border-b border-gray-200 shrink-0 px-4">
|
||||
<button onclick="showTab('t-json')" id="btn-t-json"
|
||||
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 border-blue-500 text-blue-600 -mb-px">
|
||||
JSON enviado</button>
|
||||
<button onclick="showTab('t-tns')" id="btn-t-tns"
|
||||
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-700 -mb-px">
|
||||
Respuesta TNS</button>
|
||||
<button onclick="showTab('t-msg')" id="btn-t-msg"
|
||||
class="tab-btn px-4 py-2 text-sm font-medium border-b-2 border-transparent text-gray-500 hover:text-gray-700 -mb-px">
|
||||
Mensaje</button>
|
||||
</div>
|
||||
<div class="overflow-y-auto flex-1 p-5">
|
||||
<pre id="t-json" class="tab-pane text-xs font-mono bg-gray-50 rounded-lg p-4 overflow-x-auto whitespace-pre-wrap"></pre>
|
||||
<pre id="t-tns" class="tab-pane hidden text-xs font-mono bg-yellow-50 rounded-lg p-4 overflow-x-auto whitespace-pre-wrap text-yellow-900"></pre>
|
||||
<pre id="t-msg" class="tab-pane hidden text-xs font-mono bg-blue-50 rounded-lg p-4 overflow-x-auto whitespace-pre-wrap text-blue-900"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showDetail(id, json) {
|
||||
if (!json) { showToast('No hay JSON disponible', 'info'); return; }
|
||||
const pre = document.getElementById('modal-json');
|
||||
try {
|
||||
const obj = typeof json === 'string' ? JSON.parse(json) : json;
|
||||
pre.innerHTML = syntaxHighlight(obj);
|
||||
} catch(e) {
|
||||
pre.textContent = json;
|
||||
async function verDetalle(id) {
|
||||
const resp = await fetch(`/logs/detalle/${id}`, {credentials: 'include'});
|
||||
const data = await resp.json();
|
||||
if (data.error) { showToast(data.error, 'error'); return; }
|
||||
|
||||
const estadoLabel = data.status === 'success' ? '✓ Exitoso' : '✗ Error';
|
||||
document.getElementById('modal-titulo').innerHTML =
|
||||
`<i class="fas fa-file-alt mr-2 text-blue-500"></i>Envío #${data.id} — <b>${data.tipo}</b> — <span class="${data.status === 'success' ? 'text-green-600' : 'text-red-600'}">${estadoLabel}</span>`;
|
||||
|
||||
const info = [
|
||||
data.factura ? `<span><b>Factura:</b> ${data.factura}</span>` : '',
|
||||
data.idrecepcion ? `<span><b>IDRECEPCION:</b> ${data.idrecepcion}</span>` : '',
|
||||
data.contrato ? `<span><b>Contrato:</b> ${data.contrato}</span>` : '',
|
||||
(data.fecha_inicio && data.fecha_fin) ? `<span><b>Período:</b> ${data.fecha_inicio} → ${data.fecha_fin}</span>` : '',
|
||||
data.username ? `<span><b>Usuario:</b> ${data.username}</span>` : '',
|
||||
data.created_at ? `<span><b>Enviado:</b> ${data.created_at.slice(0, 16)}</span>` : '',
|
||||
].filter(Boolean).join('');
|
||||
document.getElementById('modal-info').innerHTML = info;
|
||||
|
||||
const jsonPre = document.getElementById('t-json');
|
||||
if (data.json_enviado) {
|
||||
try { jsonPre.innerHTML = syntaxHighlight(JSON.parse(data.json_enviado)); }
|
||||
catch(e) { jsonPre.textContent = data.json_enviado; }
|
||||
} else {
|
||||
jsonPre.textContent = '(sin JSON guardado)';
|
||||
}
|
||||
|
||||
const tnsPre = document.getElementById('t-tns');
|
||||
const raw = data.respuesta_api || '(sin respuesta)';
|
||||
try { tnsPre.textContent = JSON.stringify(JSON.parse(raw), null, 2); }
|
||||
catch(e) { tnsPre.textContent = raw; }
|
||||
|
||||
document.getElementById('t-msg').textContent = data.mensaje_tns || '(sin mensaje)';
|
||||
|
||||
showTab('t-json');
|
||||
document.getElementById('modal-detalle').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function showTab(tabId) {
|
||||
document.querySelectorAll('.tab-pane').forEach(p => p.classList.add('hidden'));
|
||||
document.querySelectorAll('.tab-btn').forEach(b => {
|
||||
b.classList.remove('border-blue-500','text-blue-600');
|
||||
b.classList.add('border-transparent','text-gray-500');
|
||||
});
|
||||
document.getElementById(tabId).classList.remove('hidden');
|
||||
const btn = document.getElementById('btn-' + tabId);
|
||||
if (btn) {
|
||||
btn.classList.add('border-blue-500','text-blue-600');
|
||||
btn.classList.remove('border-transparent','text-gray-500');
|
||||
}
|
||||
document.getElementById('json-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('json-modal').classList.add('hidden');
|
||||
document.getElementById('modal-detalle').classList.add('hidden');
|
||||
}
|
||||
|
||||
function syntaxHighlight(obj) {
|
||||
let json = JSON.stringify(obj, null, 2);
|
||||
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
return json.replace(/("(?:[^"\\]|\\.)*")(?=\s*:)/g, '<span class="text-blue-600">$1</span>')
|
||||
.replace(/:(\s*)("(?:[^"\\]|\\.)*")/g, ': $1<span class="text-green-600">$2</span>')
|
||||
.replace(/:(\s*)(\d+)/g, ': $1<span class="text-orange-600">$2</span>')
|
||||
.replace(/:(\s*)(null|true|false)/g, ': $1<span class="text-purple-600">$2</span>');
|
||||
json = json.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
return json
|
||||
.replace(/("(?:[^"\\]|\\.)*")(?=\s*:)/g,'<span class="text-blue-600">$1</span>')
|
||||
.replace(/:(\s*)("(?:[^"\\]|\\.)*")/g,': $1<span class="text-green-700">$2</span>')
|
||||
.replace(/:(\s*)(\d+(?:\.\d+)?)/g,': $1<span class="text-orange-600">$2</span>')
|
||||
.replace(/:(\s*)(null|true|false)/g,': $1<span class="text-purple-600">$2</span>');
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user