feat: Funcion de sistema tenants
This commit is contained in:
@@ -17,7 +17,14 @@
|
||||
eye: 'M15 12a3 3 0 11-6 0 3 3 0 016 0z M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z',
|
||||
clock: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
check: 'M5 13l4 4L19 7',
|
||||
chevronDown: 'M19 9l-7 7-7-7'
|
||||
chevronDown: 'M19 9l-7 7-7-7',
|
||||
'building-2': 'M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4',
|
||||
'loader-2': 'M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83',
|
||||
'alert-circle': 'M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
'shield-check': 'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z',
|
||||
mail: 'M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z',
|
||||
lock: 'M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z',
|
||||
'eye-off': 'M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21'
|
||||
};
|
||||
|
||||
$: path = icons[name] || icons.home;
|
||||
|
||||
@@ -2,22 +2,25 @@
|
||||
export let ticket: import('$lib/stores/tickets').Ticket;
|
||||
|
||||
// Status mapping
|
||||
const statusConfig = {
|
||||
const statusConfig: Record<string, { label: string; class: string }> = {
|
||||
NEW: { label: 'Nuevo', class: 'badge-new' },
|
||||
IN_PROGRESS: { label: 'En Progreso', class: 'badge-in-progress' },
|
||||
WAITING_CUSTOMER: { label: 'Esperando Cliente', class: 'badge-waiting' },
|
||||
WAITING_FOR_CLIENT: { label: 'Esperando Cliente', class: 'badge-waiting' },
|
||||
RESOLVED: { label: 'Resuelto', class: 'badge-resolved' },
|
||||
CLOSED: { label: 'Cerrado', class: 'badge-closed' },
|
||||
REOPENED: { label: 'Reabierto', class: 'badge-reopened' }
|
||||
};
|
||||
const fallbackStatus = { label: 'Desconocido', class: 'badge-new' };
|
||||
|
||||
// Priority mapping
|
||||
const priorityConfig = {
|
||||
const priorityConfig: Record<string, { label: string; class: string }> = {
|
||||
LOW: { label: 'Baja', class: 'badge-priority-low' },
|
||||
MEDIUM: { label: 'Media', class: 'badge-priority-medium' },
|
||||
HIGH: { label: 'Alta', class: 'badge-priority-high' },
|
||||
URGENT: { label: 'Urgente', class: 'badge-priority-urgent' }
|
||||
};
|
||||
const fallbackPriority = { label: 'Normal', class: 'badge-priority-medium' };
|
||||
|
||||
// Format date
|
||||
function formatDate(dateString: string): string {
|
||||
@@ -58,11 +61,11 @@
|
||||
</a>
|
||||
</h3>
|
||||
<div class="flex items-center space-x-2 ml-4">
|
||||
<span class={`${statusConfig[ticket.status].class}`}>
|
||||
{statusConfig[ticket.status].label}
|
||||
<span class={(statusConfig[ticket.status] ?? fallbackStatus).class}>
|
||||
{(statusConfig[ticket.status] ?? fallbackStatus).label}
|
||||
</span>
|
||||
<span class={`${priorityConfig[ticket.priority].class}`}>
|
||||
{priorityConfig[ticket.priority].label}
|
||||
<span class={(priorityConfig[ticket.priority] ?? fallbackPriority).class}>
|
||||
{(priorityConfig[ticket.priority] ?? fallbackPriority).label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,6 +35,7 @@ async function apiCall(endpoint: string, options: RequestInit = {}) {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${authState.token}`,
|
||||
...(authState.user?.tenant_id ? { 'X-Tenant-ID': authState.user.tenant_id } : {}),
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
@@ -135,7 +135,9 @@ function createTicketsStore() {
|
||||
update((state: TicketsState) => ({ ...state, isLoading: true, error: null }));
|
||||
|
||||
try {
|
||||
const tickets = await apiCall('/tickets/');
|
||||
const raw = await apiCall('/tickets/');
|
||||
// El backend devuelve 'subject', el tipo Ticket usa 'title'
|
||||
const tickets = raw.map((t: any) => ({ ...t, title: t.subject ?? t.title }));
|
||||
update((state: TicketsState) => ({ ...state, tickets, isLoading: false }));
|
||||
} catch (error) {
|
||||
update((state: TicketsState) => ({
|
||||
@@ -151,11 +153,13 @@ function createTicketsStore() {
|
||||
update((state: TicketsState) => ({ ...state, isLoading: true, error: null }));
|
||||
|
||||
try {
|
||||
const [ticket, comments, attachments] = await Promise.all([
|
||||
const [ticketRaw, comments, attachments] = await Promise.all([
|
||||
apiCall(`/tickets/${ticketId}`),
|
||||
apiCall(`/tickets/${ticketId}/comments`),
|
||||
apiCall(`/tickets/${ticketId}/attachments`)
|
||||
]);
|
||||
// El backend devuelve 'subject', el tipo Ticket usa 'title'
|
||||
const ticket = { ...ticketRaw, title: ticketRaw.subject ?? ticketRaw.title };
|
||||
|
||||
update((state: TicketsState) => ({
|
||||
...state,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
let email = '';
|
||||
let password = '';
|
||||
let tenantSlug = 'aduanasoft-demo';
|
||||
let totpCode = '';
|
||||
let isLoading = false;
|
||||
let showTwoFactor = false;
|
||||
@@ -33,7 +34,7 @@
|
||||
await auth.login({
|
||||
email,
|
||||
password,
|
||||
tenant_slug: 'aduanasoft', // Default tenant for now
|
||||
tenant_slug: tenantSlug.trim() || 'aduanasoft-demo',
|
||||
totp_code: totpCode || undefined
|
||||
});
|
||||
|
||||
|
||||
@@ -63,22 +63,25 @@
|
||||
}
|
||||
|
||||
// Status mapping
|
||||
const statusConfig = {
|
||||
const statusConfig: Record<string, { label: string; class: string }> = {
|
||||
NEW: { label: 'Nuevo', class: 'badge-new' },
|
||||
IN_PROGRESS: { label: 'En Progreso', class: 'badge-in-progress' },
|
||||
WAITING_FOR_CLIENT: { label: 'Esperando Cliente', class: 'badge-waiting' },
|
||||
WAITING_CUSTOMER: { label: 'Esperando Cliente', class: 'badge-waiting' },
|
||||
RESOLVED: { label: 'Resuelto', class: 'badge-resolved' },
|
||||
CLOSED: { label: 'Cerrado', class: 'badge-closed' },
|
||||
REOPENED: { label: 'Reabierto', class: 'badge-reopened' }
|
||||
};
|
||||
const fallbackStatus = { label: 'Desconocido', class: 'badge-new' };
|
||||
|
||||
// Priority mapping
|
||||
const priorityConfig = {
|
||||
const priorityConfig: Record<string, { label: string; class: string }> = {
|
||||
LOW: { label: 'Baja', class: 'badge-priority-low' },
|
||||
MEDIUM: { label: 'Media', class: 'badge-priority-medium' },
|
||||
HIGH: { label: 'Alta', class: 'badge-priority-high' },
|
||||
URGENT: { label: 'Urgente', class: 'badge-priority-urgent' }
|
||||
};
|
||||
const fallbackPriority = { label: 'Normal', class: 'badge-priority-medium' };
|
||||
|
||||
async function handleAddComment() {
|
||||
if (!newComment.trim()) return;
|
||||
@@ -175,7 +178,7 @@
|
||||
// Check if user can close ticket
|
||||
$: canClose =
|
||||
$tickets.currentTicket &&
|
||||
['RESOLVED', 'WAITING_FOR_CLIENT'].includes($tickets.currentTicket.status);
|
||||
['RESOLVED', 'WAITING_CUSTOMER'].includes($tickets.currentTicket.status);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -230,11 +233,11 @@
|
||||
{$tickets.currentTicket.title}
|
||||
</h1>
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class={statusConfig[$tickets.currentTicket.status].class}>
|
||||
{statusConfig[$tickets.currentTicket.status].label}
|
||||
<span class={(statusConfig[$tickets.currentTicket.status] ?? fallbackStatus).class}>
|
||||
{(statusConfig[$tickets.currentTicket.status] ?? fallbackStatus).label}
|
||||
</span>
|
||||
<span class={priorityConfig[$tickets.currentTicket.priority].class}>
|
||||
{priorityConfig[$tickets.currentTicket.priority].label}
|
||||
<span class={(priorityConfig[$tickets.currentTicket.priority] ?? fallbackPriority).class}>
|
||||
{(priorityConfig[$tickets.currentTicket.priority] ?? fallbackPriority).label}
|
||||
</span>
|
||||
<span class="text-sm text-gray-500">
|
||||
Creado {formatDate($tickets.currentTicket.created_at)}
|
||||
@@ -505,6 +508,45 @@
|
||||
<dd class="text-sm text-gray-900">{formatDate($tickets.currentTicket.updated_at)}</dd>
|
||||
</div>
|
||||
|
||||
<!-- SLA -->
|
||||
{#if $tickets.currentTicket.sla_response_due || $tickets.currentTicket.sla_resolution_due}
|
||||
<div class="pt-3 border-t border-gray-100">
|
||||
<dt class="text-sm font-medium text-gray-500 mb-2">Tiempos de SLA</dt>
|
||||
|
||||
{#if $tickets.currentTicket.sla_response_due}
|
||||
{@const respDue = new Date($tickets.currentTicket.sla_response_due)}
|
||||
{@const respVencido = respDue < new Date() && !$tickets.currentTicket.first_response_at}
|
||||
<div class="mb-2">
|
||||
<dt class="text-xs text-gray-400">Respuesta límite</dt>
|
||||
<dd class="text-sm {respVencido ? 'text-red-600 font-medium' : 'text-gray-900'}">
|
||||
{formatDate($tickets.currentTicket.sla_response_due)}
|
||||
{#if respVencido}
|
||||
<span class="block text-xs text-red-500">¡Vencido!</span>
|
||||
{:else if $tickets.currentTicket.first_response_at}
|
||||
<span class="block text-xs text-green-600">✓ Respondido</span>
|
||||
{/if}
|
||||
</dd>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $tickets.currentTicket.sla_resolution_due}
|
||||
{@const resDue = new Date($tickets.currentTicket.sla_resolution_due)}
|
||||
{@const resVencido = resDue < new Date() && !$tickets.currentTicket.resolved_at}
|
||||
<div>
|
||||
<dt class="text-xs text-gray-400">Resolución límite</dt>
|
||||
<dd class="text-sm {resVencido ? 'text-red-600 font-medium' : 'text-gray-900'}">
|
||||
{formatDate($tickets.currentTicket.sla_resolution_due)}
|
||||
{#if resVencido}
|
||||
<span class="block text-xs text-red-500">¡Vencido!</span>
|
||||
{:else if $tickets.currentTicket.resolved_at}
|
||||
<span class="block text-xs text-green-600">✓ Resuelto</span>
|
||||
{/if}
|
||||
</dd>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $tickets.currentTicket.due_date}
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">Fecha límite</dt>
|
||||
|
||||
Reference in New Issue
Block a user