Feature: Rediseño completo con tema día/noche y fondos animados
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 54s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 54s
- Implementar sistema de tema día/noche con persistencia en localStorage - Crear componente AnimatedBackground con paisajes SVG animados - Generar todos los assets SVG desde cero (sol, luna, estrellas, nubes, montañas) - Añadir animaciones suaves para nubes, estrellas y elementos del paisaje - Rediseñar UserHeader como componente principal clickeable - Integrar modal de edición de perfil en el header - Reorganizar layout principal mostrando solo aplicaciones - Mejorar diseño de ApplicationsList con glassmorphism - Implementar efectos hover y transiciones elegantes - Diseño responsive mobile-first - Diferencias visuales notorias entre modo día y noche
This commit is contained in:
@@ -1,89 +1,87 @@
|
||||
<template>
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="font-semibold text-lg flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-squares-2x2" />
|
||||
Mis Aplicaciones
|
||||
</h3>
|
||||
<UBadge v-if="filteredApplications.length > 0" color="primary" variant="soft">
|
||||
{{ filteredApplications.length }}
|
||||
</UBadge>
|
||||
</div>
|
||||
|
||||
<!-- Filtros por grupos -->
|
||||
<div v-if="availableGroups.length > 0" class="flex flex-wrap gap-2">
|
||||
<UButton
|
||||
v-for="group in availableGroups"
|
||||
:key="group"
|
||||
size="sm"
|
||||
:color="selectedGroups.includes(group) ? 'primary' : 'neutral'"
|
||||
:variant="selectedGroups.includes(group) ? 'soft' : 'ghost'"
|
||||
@click="toggleGroup(group)"
|
||||
>
|
||||
{{ group }}
|
||||
</UButton>
|
||||
</div>
|
||||
<div class="applications-container">
|
||||
<div class="applications-header">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="applications-title">
|
||||
<UIcon name="i-heroicons-squares-2x2" class="w-6 h-6" />
|
||||
Mis Aplicaciones
|
||||
</h2>
|
||||
<UBadge v-if="filteredApplications.length > 0" color="primary" variant="soft" size="lg">
|
||||
{{ filteredApplications.length }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="pending" class="flex justify-center py-8">
|
||||
<UIcon name="i-heroicons-arrow-path" class="w-8 h-8 animate-spin text-primary" />
|
||||
<!-- Filtros por grupos -->
|
||||
<div v-if="availableGroups.length > 0" class="filter-section">
|
||||
<UButton
|
||||
v-for="group in availableGroups"
|
||||
:key="group"
|
||||
size="sm"
|
||||
:color="selectedGroups.includes(group) ? 'primary' : 'neutral'"
|
||||
:variant="selectedGroups.includes(group) ? 'soft' : 'ghost'"
|
||||
@click="toggleGroup(group)"
|
||||
>
|
||||
{{ group }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="text-center py-8">
|
||||
<UIcon name="i-heroicons-exclamation-triangle" class="w-12 h-12 mx-auto mb-4 text-error" />
|
||||
<p class="text-error font-semibold">Error al cargar aplicaciones</p>
|
||||
<div v-if="pending" class="empty-state">
|
||||
<UIcon name="i-heroicons-arrow-path" class="w-12 h-12 animate-spin text-primary" />
|
||||
<p class="mt-4 text-gray-600 dark:text-gray-400">Cargando aplicaciones...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="empty-state">
|
||||
<UIcon name="i-heroicons-exclamation-triangle" class="w-16 h-16 text-error" />
|
||||
<p class="mt-4 text-error font-semibold text-lg">Error al cargar aplicaciones</p>
|
||||
<p class="text-sm text-gray-500 mt-2">{{ error.message }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="applications.length === 0" class="text-center py-8">
|
||||
<UIcon name="i-heroicons-inbox" class="w-12 h-12 mx-auto mb-4 text-gray-400" />
|
||||
<p class="text-gray-600 dark:text-gray-400">No tienes aplicaciones disponibles</p>
|
||||
<div v-else-if="applications.length === 0" class="empty-state">
|
||||
<UIcon name="i-heroicons-inbox" class="w-16 h-16 text-gray-400" />
|
||||
<p class="mt-4 text-gray-600 dark:text-gray-400 text-lg">No tienes aplicaciones disponibles</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="filteredApplications.length === 0" class="text-center py-8">
|
||||
<UIcon name="i-heroicons-funnel" class="w-12 h-12 mx-auto mb-4 text-gray-400" />
|
||||
<p class="text-gray-600 dark:text-gray-400">No hay aplicaciones en los grupos seleccionados</p>
|
||||
<div v-else-if="filteredApplications.length === 0" class="empty-state">
|
||||
<UIcon name="i-heroicons-funnel" class="w-16 h-16 text-gray-400" />
|
||||
<p class="mt-4 text-gray-600 dark:text-gray-400 text-lg">No hay aplicaciones en los grupos seleccionados</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div v-else class="applications-grid">
|
||||
<a
|
||||
v-for="app in filteredApplications"
|
||||
:key="app.pk"
|
||||
:href="app.launchUrl"
|
||||
:target="app.openInNewTab ? '_blank' : '_self'"
|
||||
class="group block p-4 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-primary hover:bg-primary/5 transition-all duration-200"
|
||||
class="app-card"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0 w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||
<div class="app-card-content">
|
||||
<div class="app-icon">
|
||||
<UIcon
|
||||
v-if="app.icon"
|
||||
:name="app.icon"
|
||||
class="w-6 h-6 text-primary"
|
||||
class="w-7 h-7"
|
||||
/>
|
||||
<UIcon
|
||||
v-else
|
||||
name="i-heroicons-cube"
|
||||
class="w-6 h-6 text-primary"
|
||||
class="w-7 h-7"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<h4 class="font-semibold text-sm group-hover:text-primary transition-colors">
|
||||
<div class="app-info">
|
||||
<div class="app-header">
|
||||
<h4 class="app-name">
|
||||
{{ app.name }}
|
||||
</h4>
|
||||
<UIcon
|
||||
v-if="app.openInNewTab"
|
||||
name="i-heroicons-arrow-top-right-on-square"
|
||||
class="w-4 h-4 text-gray-400 flex-shrink-0"
|
||||
class="external-icon"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Grupos como subtítulo con chips compactos -->
|
||||
<div v-if="app.group" class="mt-1 flex flex-wrap gap-1">
|
||||
<div v-if="app.group" class="app-groups">
|
||||
<UBadge
|
||||
v-for="group in app.group.split(',')"
|
||||
:key="group"
|
||||
@@ -95,14 +93,14 @@
|
||||
</UBadge>
|
||||
</div>
|
||||
|
||||
<p v-if="app.description" class="text-xs text-gray-500 dark:text-gray-400 mt-2 line-clamp-2">
|
||||
<p v-if="app.description" class="app-description">
|
||||
{{ app.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -175,3 +173,211 @@ onUnmounted(() => {
|
||||
clearInterval(refreshInterval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.applications-container {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(15px);
|
||||
border-radius: 1.5rem;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
:global(.dark) .applications-container {
|
||||
background: rgba(30, 30, 40, 0.85);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.applications-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.applications-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-gray-900);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:global(.dark) .applications-title {
|
||||
color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4rem 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.applications-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.applications-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.applications-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.app-card {
|
||||
display: block;
|
||||
padding: 1.25rem;
|
||||
border-radius: 1rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 2px solid rgba(var(--color-gray-200), 0.5);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
:global(.dark) .app-card {
|
||||
background: rgba(40, 40, 50, 0.9);
|
||||
border-color: rgba(var(--color-gray-700), 0.5);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.app-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 16px rgba(var(--color-primary-500), 0.2);
|
||||
border-color: rgb(var(--color-primary-500));
|
||||
}
|
||||
|
||||
:global(.dark) .app-card:hover {
|
||||
box-shadow: 0 8px 16px rgba(var(--color-primary-500), 0.3);
|
||||
}
|
||||
|
||||
.app-card-content {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
flex-shrink: 0;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 0.75rem;
|
||||
background: rgba(var(--color-primary-500), 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgb(var(--color-primary-500));
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.app-card:hover .app-icon {
|
||||
background: rgba(var(--color-primary-500), 0.2);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.app-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-gray-900);
|
||||
margin: 0;
|
||||
transition: color 0.3s ease;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
:global(.dark) .app-name {
|
||||
color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.app-card:hover .app-name {
|
||||
color: rgb(var(--color-primary-500));
|
||||
}
|
||||
|
||||
.external-icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
color: var(--color-gray-400);
|
||||
flex-shrink: 0;
|
||||
margin-top: 0.125rem;
|
||||
}
|
||||
|
||||
.app-groups {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.app-description {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-gray-600);
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:global(.dark) .app-description {
|
||||
color: var(--color-gray-400);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
.applications-container {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.applications-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.applications-grid {
|
||||
gap: 0.75rem;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.app-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.app-card-content {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user