avances poderosos en ui
This commit is contained in:
1
ui/src/components/asistencias/cardAsistencia.vue
Normal file
1
ui/src/components/asistencias/cardAsistencia.vue
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
ui/src/components/asistencias/tablaAsistencias.vue
Normal file
1
ui/src/components/asistencias/tablaAsistencias.vue
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
83
ui/src/components/chat/CanvasChat.vue
Normal file
83
ui/src/components/chat/CanvasChat.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<script setup>
|
||||
import { ref, nextTick, onMounted, watch } from 'vue'
|
||||
import { useChat } from '@/stores/useChat'
|
||||
|
||||
const chat = useChat()
|
||||
const msg = ref('')
|
||||
const list = ref(null)
|
||||
|
||||
function scrollBottom () {
|
||||
nextTick(() => list.value?.scrollTo({ top: list.value.scrollHeight, behavior: 'smooth' }))
|
||||
}
|
||||
|
||||
function send () {
|
||||
const t = msg.value.trim()
|
||||
if (!t) return
|
||||
|
||||
if (t.startsWith('/')) chat.run(t.slice(1))
|
||||
else chat.add({ type: 'text', owner: 'yo', text: t })
|
||||
|
||||
msg.value = ''
|
||||
scrollBottom()
|
||||
}
|
||||
|
||||
function handleKey (e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
send()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!chat.items.length) {
|
||||
chat.add({ type: 'text', owner: 'bot', text: '¡Hola! Probá /empleados, /tareas, etc.' })
|
||||
}
|
||||
scrollBottom()
|
||||
})
|
||||
|
||||
watch(() => chat.items.length, scrollBottom)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- se adapta al contenedor flex, sin superponer la sidebar -->
|
||||
<div class="flex flex-col flex-1 min-h-0 bg-gray-50">
|
||||
<!-- historial -->
|
||||
<div ref="list" class="flex-1 min-h-0 overflow-auto p-6 space-y-4 custom-scroll">
|
||||
<template v-for="(m,i) in chat.items" :key="i">
|
||||
<!-- mensaje de texto -->
|
||||
<div :class="m.owner==='yo' ? 'flex justify-end' : 'flex justify-start'" v-if="m.type==='text'">
|
||||
<div
|
||||
class="max-w-lg rounded-lg px-4 py-2 shadow break-words"
|
||||
:class="m.owner==='yo' ? 'bg-teal-600 text-white' : 'bg-white text-gray-900'">
|
||||
{{ m.text }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- componente dinámico -->
|
||||
<component v-else :is="m.is" v-bind="m.props" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- input -->
|
||||
<form @submit.prevent="send" class="border-t bg-white p-4 flex gap-2">
|
||||
<textarea
|
||||
v-model="msg"
|
||||
@keydown="handleKey"
|
||||
rows="1"
|
||||
placeholder="Escribí un mensaje… (Enter para enviar, Shift+Enter salto)"
|
||||
class="flex-1 resize-none rounded-lg border p-3 focus:outline-none focus:ring-2 focus:ring-teal-500 custom-scroll"
|
||||
/>
|
||||
<button type="submit" class="px-4 py-2 rounded-lg bg-teal-600 text-white hover:bg-teal-700 transition">
|
||||
➤
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-scroll::-webkit-scrollbar { width: 8px; }
|
||||
.custom-scroll::-webkit-scrollbar-track { background: transparent; }
|
||||
.custom-scroll::-webkit-scrollbar-thumb { background-color: rgba(13,148,136,.35); border-radius: 4px; }
|
||||
.custom-scroll:hover::-webkit-scrollbar-thumb { background-color: rgba(13,148,136,.7); }
|
||||
.custom-scroll { scrollbar-width: thin; scrollbar-color: rgba(13,148,136,.6) transparent; }
|
||||
</style>
|
||||
1
ui/src/components/empleados/cardEmpleado.vue
Normal file
1
ui/src/components/empleados/cardEmpleado.vue
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
ui/src/components/empleados/tablaEmpleados.vue
Normal file
1
ui/src/components/empleados/tablaEmpleados.vue
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
ui/src/components/planillas/cardPlanilla.vue
Normal file
1
ui/src/components/planillas/cardPlanilla.vue
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
ui/src/components/planillas/tablaPlanillas.vue
Normal file
1
ui/src/components/planillas/tablaPlanillas.vue
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
ui/src/components/tareas/cardTarea.vue
Normal file
1
ui/src/components/tareas/cardTarea.vue
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
ui/src/components/tareas/tablaTareas.vue
Normal file
1
ui/src/components/tareas/tablaTareas.vue
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
71
ui/src/components/ui/NavBar.vue
Normal file
71
ui/src/components/ui/NavBar.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<script setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useUi } from '@/stores/useUi'
|
||||
|
||||
const ui = useUi()
|
||||
|
||||
// enlaces de la app
|
||||
const links = [
|
||||
{ to: '/', label: 'Chat', icon: '💬' },
|
||||
{ to: '/empleados', label: 'Empleados', icon: '👥' },
|
||||
{ to: '/tareas', label: 'Tareas', icon: '📋' },
|
||||
{ to: '/planillas', label: 'Planillas', icon: '📂' },
|
||||
{ to: '/asistencias', label: 'Asistencias', icon: '⏰' },
|
||||
{ to: '/config', label: 'Config', icon: '⚙️' },
|
||||
]
|
||||
|
||||
const route = useRoute()
|
||||
const activePath = ref(route.path)
|
||||
watch(route, v => (activePath.value = v.path))
|
||||
|
||||
// clases dinámicas p/ mostrar / ocultar barra
|
||||
const sidebarClasses = computed(() => ui.sidebarOpen ? 'translate-x-0' : '-translate-x-full')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- backdrop en mobile -->
|
||||
<div v-if="ui.sidebarOpen" class="fixed inset-0 bg-black/40 md:hidden" @click="ui.closeSidebar" />
|
||||
|
||||
<!-- barra lateral -->
|
||||
<aside
|
||||
:class="['fixed left-0 top-0 md:top-14 h-screen w-60 bg-white dark:bg-zinc-900 border-r border-gray-200 dark:border-zinc-800 flex flex-col select-none z-50 transform transition-transform duration-200 ease-in-out', sidebarClasses]">
|
||||
|
||||
<!-- encabezado dentro de sidebar -->
|
||||
<div class="flex items-center justify-between px-4 py-4 md:px-5 md:py-4 border-b border-gray-200 dark:border-zinc-800 md:border-none">
|
||||
<span class="text-lg font-semibold text-teal-600 dark:text-teal-400 md:hidden">Núcleo</span>
|
||||
<button class="h-8 w-8 inline-flex items-center justify-center text-gray-500 hover:text-teal-600" @click="ui.toggleSidebar">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- navegación -->
|
||||
<nav class="flex-1 overflow-y-auto custom-scroll pr-1 pt-4 md:pt-0">
|
||||
<ul class="space-y-1 px-2">
|
||||
<li v-for="l in links" :key="l.to">
|
||||
<RouterLink
|
||||
:to="l.to"
|
||||
class="flex items-center gap-3 w-full px-3 py-2 rounded-md font-medium transition group"
|
||||
:class="activePath.startsWith(l.to)
|
||||
? 'bg-teal-600 text-white shadow'
|
||||
: 'text-gray-700 dark:text-gray-100 hover:bg-teal-100 hover:text-teal-900 dark:hover:bg-zinc-800'"
|
||||
@click="ui.closeSidebar()"
|
||||
>
|
||||
<span class="text-lg" aria-hidden="true">{{ l.icon }}</span>
|
||||
<span class="truncate">{{ l.label }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
ul { list-style: none; padding-left: 0; }
|
||||
|
||||
.custom-scroll::-webkit-scrollbar { width: 8px; }
|
||||
.custom-scroll::-webkit-scrollbar-track { background: transparent; }
|
||||
.custom-scroll::-webkit-scrollbar-thumb { background-color: rgba(13,148,136,.4); border-radius: 4px; }
|
||||
.custom-scroll:hover::-webkit-scrollbar-thumb { background-color: rgba(13,148,136,.7); }
|
||||
.custom-scroll { scrollbar-width: thin; scrollbar-color: rgba(13,148,136,.6) transparent; }
|
||||
</style>
|
||||
0
ui/src/components/ui/SideDrawer.vue
Normal file
0
ui/src/components/ui/SideDrawer.vue
Normal file
23
ui/src/components/ui/TopBar.vue
Normal file
23
ui/src/components/ui/TopBar.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
import { useUi } from '@/stores/useUi'
|
||||
const ui = useUi()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- barra superior fija -->
|
||||
<header class="fixed top-0 left-0 right-0 h-14 bg-white dark:bg-zinc-900 border-b border-gray-200 dark:border-zinc-800 flex items-center justify-between px-4 md:px-6 z-50 shadow-sm">
|
||||
<!-- título -->
|
||||
<h1 class="text-lg font-semibold tracking-wide text-teal-600 dark:text-teal-400 select-none">Núcleo</h1>
|
||||
|
||||
<!-- botón hamburguesa (visible solo en mobile) -->
|
||||
<button
|
||||
@click="ui.toggleSidebar"
|
||||
class="inline-flex items-center justify-center h-9 w-9 rounded-md bg-teal-600 text-white hover:bg-teal-700 transition ">
|
||||
☰
|
||||
</button>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* sin estilos extra */
|
||||
</style>
|
||||
Reference in New Issue
Block a user