Initial commit

This commit is contained in:
2026-01-12 08:17:17 -07:00
commit de5b6feef4
104 changed files with 12925 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
<script lang="ts">
export let ticket: import('$lib/stores/tickets').Ticket;
// Status mapping
const statusConfig = {
NEW: { label: 'Nuevo', class: 'badge-new' },
IN_PROGRESS: { label: 'En Progreso', class: 'badge-in-progress' },
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' }
};
// Priority mapping
const priorityConfig = {
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' }
};
// Format date
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString('es-ES', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
// Format relative time
function formatRelativeTime(dateString: string): string {
const date = new Date(dateString);
const now = new Date();
const diffInMinutes = Math.floor((now.getTime() - date.getTime()) / (1000 * 60));
if (diffInMinutes < 1) return 'hace un momento';
if (diffInMinutes < 60) return `hace ${diffInMinutes}m`;
const diffInHours = Math.floor(diffInMinutes / 60);
if (diffInHours < 24) return `hace ${diffInHours}h`;
const diffInDays = Math.floor(diffInHours / 24);
if (diffInDays < 7) return `hace ${diffInDays}d`;
return formatDate(dateString);
}
</script>
<div class="card hover:shadow-md transition-shadow">
<div class="card-content">
<div class="flex justify-between items-start mb-3">
<h3 class="text-lg font-medium text-gray-900 line-clamp-2">
<a href="/tickets/{ticket.id}" class="hover:text-primary-600">
{ticket.title}
</a>
</h3>
<div class="flex items-center space-x-2 ml-4">
<span class={`${statusConfig[ticket.status].class}`}>
{statusConfig[ticket.status].label}
</span>
<span class={`${priorityConfig[ticket.priority].class}`}>
{priorityConfig[ticket.priority].label}
</span>
</div>
</div>
<p class="text-gray-600 text-sm line-clamp-3 mb-4">
{ticket.description}
</p>
<div class="flex justify-between items-center text-xs text-gray-500">
<div class="flex items-center space-x-4">
<span>#{ticket.id.substring(0, 8)}</span>
{#if ticket.category_name}
<span class="bg-gray-100 text-gray-600 px-2 py-1 rounded">
{ticket.category_name}
</span>
{/if}
{#if ticket.assigned_to_name}
<span class="flex items-center space-x-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
<span>{ticket.assigned_to_name}</span>
</span>
{/if}
</div>
<div class="flex items-center space-x-3">
{#if ticket.due_date}
<span class="flex items-center space-x-1 {new Date(ticket.due_date) < new Date() ? 'text-red-600' : ''}">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>Vence {formatRelativeTime(ticket.due_date)}</span>
</span>
{/if}
<span title={formatDate(ticket.updated_at)}>
Actualizado {formatRelativeTime(ticket.updated_at)}
</span>
</div>
</div>
</div>
</div>
<style>
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>