avances poderosos en ui
This commit is contained in:
0
api/routes/clientesRandom.js
Normal file
0
api/routes/clientesRandom.js
Normal file
@@ -1,12 +1,11 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { PrismaClient } from './prisma/generated/client/index.js';
|
import { PrismaClient } from './prisma/generated/client/index.js';
|
||||||
import { Decimal } from '@prisma/client/runtime/library.js';
|
import { Decimal } from '@prisma/client/runtime/library.js';
|
||||||
|
|
||||||
BigInt.prototype.toJSON = function () { return this.toString(); };
|
BigInt.prototype.toJSON = function () { return this.toString(); };
|
||||||
Decimal.prototype.toJSON = function () { return this.toString(); };
|
Decimal.prototype.toJSON = function () { return this.toString(); };
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
const app = express();
|
export const app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
app.get('/api/test', (req, res) => res.json({ message: 'Hello World' }));
|
app.get('/api/test', (req, res) => res.json({ message: 'Hello World' }));
|
||||||
|
|||||||
@@ -31,10 +31,11 @@ C:\no guardar\nucleo V3\planilla\agent\respuestas\respuestaNormal.js
|
|||||||
C:\no guardar\nucleo V3\planilla\agent\utils\decryptMediaContent.js
|
C:\no guardar\nucleo V3\planilla\agent\utils\decryptMediaContent.js
|
||||||
C:\no guardar\nucleo V3\planilla\agent\utils\processMessage.js
|
C:\no guardar\nucleo V3\planilla\agent\utils\processMessage.js
|
||||||
C:\no guardar\nucleo V3\planilla\agent\utils\saveMedia.js
|
C:\no guardar\nucleo V3\planilla\agent\utils\saveMedia.js
|
||||||
C:\no guardar\nucleo V3\planilla\api\cron_jobs
|
|
||||||
C:\no guardar\nucleo V3\planilla\api\dev
|
C:\no guardar\nucleo V3\planilla\api\dev
|
||||||
C:\no guardar\nucleo V3\planilla\api\prisma
|
C:\no guardar\nucleo V3\planilla\api\prisma
|
||||||
|
C:\no guardar\nucleo V3\planilla\api\routes
|
||||||
C:\no guardar\nucleo V3\planilla\api\.env
|
C:\no guardar\nucleo V3\planilla\api\.env
|
||||||
|
C:\no guardar\nucleo V3\planilla\api\.env.example
|
||||||
C:\no guardar\nucleo V3\planilla\api\Dockerfile
|
C:\no guardar\nucleo V3\planilla\api\Dockerfile
|
||||||
C:\no guardar\nucleo V3\planilla\api\entrypoint.sh
|
C:\no guardar\nucleo V3\planilla\api\entrypoint.sh
|
||||||
C:\no guardar\nucleo V3\planilla\api\package-lock.json
|
C:\no guardar\nucleo V3\planilla\api\package-lock.json
|
||||||
@@ -42,6 +43,7 @@ C:\no guardar\nucleo V3\planilla\api\package.json
|
|||||||
C:\no guardar\nucleo V3\planilla\api\server.js
|
C:\no guardar\nucleo V3\planilla\api\server.js
|
||||||
C:\no guardar\nucleo V3\planilla\api\dev\docker-compose.yml
|
C:\no guardar\nucleo V3\planilla\api\dev\docker-compose.yml
|
||||||
C:\no guardar\nucleo V3\planilla\api\prisma\generated
|
C:\no guardar\nucleo V3\planilla\api\prisma\generated
|
||||||
|
C:\no guardar\nucleo V3\planilla\api\prisma\migrations
|
||||||
C:\no guardar\nucleo V3\planilla\api\prisma\schema.prisma
|
C:\no guardar\nucleo V3\planilla\api\prisma\schema.prisma
|
||||||
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client
|
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client
|
||||||
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime
|
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime
|
||||||
@@ -67,6 +69,10 @@ C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime\library.d.t
|
|||||||
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime\library.js
|
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime\library.js
|
||||||
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime\react-native.js
|
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime\react-native.js
|
||||||
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime\wasm.js
|
C:\no guardar\nucleo V3\planilla\api\prisma\generated\client\runtime\wasm.js
|
||||||
|
C:\no guardar\nucleo V3\planilla\api\prisma\migrations\20250515020056_init
|
||||||
|
C:\no guardar\nucleo V3\planilla\api\prisma\migrations\migration_lock.toml
|
||||||
|
C:\no guardar\nucleo V3\planilla\api\prisma\migrations\20250515020056_init\migration.sql
|
||||||
|
C:\no guardar\nucleo V3\planilla\api\routes\clientesRandom.js
|
||||||
C:\no guardar\nucleo V3\planilla\dev\scripts
|
C:\no guardar\nucleo V3\planilla\dev\scripts
|
||||||
C:\no guardar\nucleo V3\planilla\dev\scripts\estructura.ps1
|
C:\no guardar\nucleo V3\planilla\dev\scripts\estructura.ps1
|
||||||
C:\no guardar\nucleo V3\planilla\mcp\Dockerfile
|
C:\no guardar\nucleo V3\planilla\mcp\Dockerfile
|
||||||
@@ -84,11 +90,53 @@ C:\no guardar\nucleo V3\planilla\ui\vite.config.js
|
|||||||
C:\no guardar\nucleo V3\planilla\ui\public\vite.svg
|
C:\no guardar\nucleo V3\planilla\ui\public\vite.svg
|
||||||
C:\no guardar\nucleo V3\planilla\ui\src\assets
|
C:\no guardar\nucleo V3\planilla\ui\src\assets
|
||||||
C:\no guardar\nucleo V3\planilla\ui\src\components
|
C:\no guardar\nucleo V3\planilla\ui\src\components
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\layouts
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\router
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\stores
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\tests
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views
|
||||||
C:\no guardar\nucleo V3\planilla\ui\src\App.vue
|
C:\no guardar\nucleo V3\planilla\ui\src\App.vue
|
||||||
C:\no guardar\nucleo V3\planilla\ui\src\main.js
|
C:\no guardar\nucleo V3\planilla\ui\src\main.js
|
||||||
C:\no guardar\nucleo V3\planilla\ui\src\style.css
|
C:\no guardar\nucleo V3\planilla\ui\src\style.css
|
||||||
C:\no guardar\nucleo V3\planilla\ui\src\assets\vue.svg
|
C:\no guardar\nucleo V3\planilla\ui\src\assets\vue.svg
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\asistencias
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\chat
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\empleados
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\planillas
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\tareas
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\ui
|
||||||
C:\no guardar\nucleo V3\planilla\ui\src\components\HelloWorld.vue
|
C:\no guardar\nucleo V3\planilla\ui\src\components\HelloWorld.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\asistencias\cardAsistencia.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\asistencias\tablaAsistencias.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\chat\CanvasChat.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\empleados\cardEmpleado.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\empleados\tablaEmpleados.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\planillas\cardPlanilla.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\planillas\tablaPlanillas.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\tareas\cardTarea.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\tareas\tablaTareas.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\ui\navBar.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\components\ui\sideDrawer.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\router\index.js
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\stores\useAsistencias.js
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\stores\useChat.js
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\stores\useEmpleados.js
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\stores\usePlanillas.js
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\stores\useTareas.js
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\asistencias
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\empleados
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\planillas
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\tareas
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\ChatView.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\SettingsView.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\asistencias\AsistenciaForm.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\asistencias\AsistenciasIndex.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\empleados\EmpleadoForm.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\empleados\EmpleadosIndex.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\planillas\PlanillaForm.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\planillas\PlanillasIndex.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\tareas\TareaForm.vue
|
||||||
|
C:\no guardar\nucleo V3\planilla\ui\src\views\tareas\TareasIndex.vue
|
||||||
C:\no guardar\nucleo V3\planilla\worker\cron
|
C:\no guardar\nucleo V3\planilla\worker\cron
|
||||||
C:\no guardar\nucleo V3\planilla\worker\prisma
|
C:\no guardar\nucleo V3\planilla\worker\prisma
|
||||||
C:\no guardar\nucleo V3\planilla\worker\.env
|
C:\no guardar\nucleo V3\planilla\worker\.env
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="es">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + Vue</title>
|
<meta name="theme-color" content="#0d9488" /> <!-- color principal de la app -->
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<title>Núcleo App</title>
|
||||||
|
<!-- Si luego querés PWA, activá el manifest -->
|
||||||
|
<!-- <link rel="manifest" href="/manifest.webmanifest" /> -->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="min-h-screen flex flex-col bg-gray-100 text-gray-900 antialiased">
|
||||||
<div id="app"></div>
|
<div id="app" class="flex-1 flex flex-col"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
1516
ui/package-lock.json
generated
1516
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,10 +9,16 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.5.13"
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
|
"pinia": "^3.0.2",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.2",
|
"@vitejs/plugin-vue": "^5.2.2",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
|
"postcss": "^8.5.3",
|
||||||
|
"tailwindcss": "^4.1.7",
|
||||||
"vite": "^6.3.1"
|
"vite": "^6.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,24 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import HelloWorld from './components/HelloWorld.vue'
|
import TopBar from '@/components/ui/TopBar.vue'
|
||||||
|
import NavBar from '@/components/ui/NavBar.vue'
|
||||||
|
import { useUi } from '@/stores/useUi'
|
||||||
|
const ui = useUi()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<!-- TopBar fija -->
|
||||||
<a href="https://vite.dev" target="_blank">
|
<TopBar />
|
||||||
<img src="/vite.svg" class="logo" alt="Vite logo" />
|
|
||||||
</a>
|
<!-- wrapper: deja espacio para TopBar (pt-14 = 56px) y, en desktop, para NavBar (pl-60) -->
|
||||||
<a href="https://vuejs.org/" target="_blank">
|
<div :class="['pt-14 min-h-screen bg-gray-100 text-gray-900 transition-[padding-left] duration-200', ui.sidebarOpen ? 'md:pl-60' : '']">
|
||||||
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
|
<!-- NavBar fija -->
|
||||||
</a>
|
<NavBar />
|
||||||
|
|
||||||
|
<!-- contenido principal -->
|
||||||
|
<main class="min-h-[calc(100vh-56px)] flex flex-col overflow-hidden">
|
||||||
|
<RouterView class="flex-1 overflow-auto" />
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<HelloWorld msg="Vite + Vue" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
.logo {
|
|
||||||
height: 6em;
|
|
||||||
padding: 1.5em;
|
|
||||||
will-change: filter;
|
|
||||||
transition: filter 300ms;
|
|
||||||
}
|
|
||||||
.logo:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #646cffaa);
|
|
||||||
}
|
|
||||||
.logo.vue:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #42b883aa);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
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>
|
||||||
@@ -1,5 +1,14 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import './style.css'
|
import { createPinia } from 'pinia'
|
||||||
import App from './App.vue'
|
import router from './router'
|
||||||
|
|
||||||
createApp(App).mount('#app')
|
import App from './App.vue'
|
||||||
|
import './style.css' // Tailwind o tus estilos globales
|
||||||
|
|
||||||
|
const app =
|
||||||
|
createApp(App)
|
||||||
|
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
|
|||||||
35
ui/src/router/index.js
Normal file
35
ui/src/router/index.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
// Chat principal y config
|
||||||
|
{ path: '/', name: 'chat', component: () => import('@/views/ChatView.vue') },
|
||||||
|
{ path: '/config', name: 'settings', component: () => import('@/views/SettingsView.vue') },
|
||||||
|
|
||||||
|
// ────── Empleados ──────
|
||||||
|
{ path: '/empleados', name: 'empleados-index', component: () => import('@/views/empleados/EmpleadosIndex.vue') },
|
||||||
|
{ path: '/empleados/nuevo', name: 'empleados-new', component: () => import('@/views/empleados/EmpleadoForm.vue') },
|
||||||
|
{ path: '/empleados/:id', name: 'empleados-edit', component: () => import('@/views/empleados/EmpleadoForm.vue'), props: true },
|
||||||
|
|
||||||
|
// ────── Tareas ──────
|
||||||
|
{ path: '/tareas', name: 'tareas-index', component: () => import('@/views/tareas/TareasIndex.vue') },
|
||||||
|
{ path: '/tareas/nuevo', name: 'tareas-new', component: () => import('@/views/tareas/TareaForm.vue') },
|
||||||
|
{ path: '/tareas/:id', name: 'tareas-edit', component: () => import('@/views/tareas/TareaForm.vue'), props: true },
|
||||||
|
|
||||||
|
// ────── Planillas ──────
|
||||||
|
{ path: '/planillas', name: 'planillas-index', component: () => import('@/views/planillas/PlanillasIndex.vue') },
|
||||||
|
{ path: '/planillas/nuevo', name: 'planillas-new', component: () => import('@/views/planillas/PlanillaForm.vue') },
|
||||||
|
{ path: '/planillas/:id', name: 'planillas-edit', component: () => import('@/views/planillas/PlanillaForm.vue'), props: true },
|
||||||
|
|
||||||
|
// ────── Asistencias ──────
|
||||||
|
{ path: '/asistencias', name: 'asistencias-index', component: () => import('@/views/asistencias/AsistenciasIndex.vue') },
|
||||||
|
{ path: '/asistencias/nuevo', name: 'asistencias-new', component: () => import('@/views/asistencias/AsistenciaForm.vue') },
|
||||||
|
{ path: '/asistencias/:id', name: 'asistencias-edit', component: () => import('@/views/asistencias/AsistenciaForm.vue'), props: true },
|
||||||
|
|
||||||
|
// 404
|
||||||
|
{ path: '/:pathMatch(.*)*', name: 'not-found', component: () => import('@/views/NotFound.vue') }
|
||||||
|
]
|
||||||
|
|
||||||
|
export default createRouter({
|
||||||
|
history: createMemoryHistory(),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
5
ui/src/stores/useAsistencias.js
Normal file
5
ui/src/stores/useAsistencias.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useAsistencias = defineStore('asistencias', {
|
||||||
|
state: () => ({ asistencias: [] }),
|
||||||
|
})
|
||||||
42
ui/src/stores/useChat.js
Normal file
42
ui/src/stores/useChat.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chat store
|
||||||
|
* - items: hist de mensajes [{ type, owner, text, … }]
|
||||||
|
* - add(): agrega un mensaje
|
||||||
|
* - run(): ejecuta /comandos y mete componentes dinámicos
|
||||||
|
*/
|
||||||
|
export const useChat = defineStore('chat', {
|
||||||
|
state: () => ({
|
||||||
|
items: [],
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
add (item) {
|
||||||
|
this.items.push(item)
|
||||||
|
},
|
||||||
|
|
||||||
|
async run (cmd) {
|
||||||
|
switch (cmd) {
|
||||||
|
case 'empleados':
|
||||||
|
// para demo solo mostramos un texto, después inyectaremos un componente
|
||||||
|
this.add({ type: 'text', owner: 'bot', text: '🎉 Módulo Empleados aún en construcción.' })
|
||||||
|
break
|
||||||
|
case 'tareas':
|
||||||
|
this.add({ type: 'text', owner: 'bot', text: '🛠️ Módulo Tareas aún en construcción.' })
|
||||||
|
break
|
||||||
|
case 'planillas':
|
||||||
|
this.add({ type: 'text', owner: 'bot', text: '📂 Módulo Planillas en construcción.' })
|
||||||
|
break
|
||||||
|
case 'asistencias':
|
||||||
|
this.add({ type: 'text', owner: 'bot', text: '⏰ Módulo Asistencias en construcción.' })
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this.add({ type: 'text', owner: 'bot', text: `❓ No reconozco /${cmd}` })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
12
ui/src/stores/useEmpleados.js
Normal file
12
ui/src/stores/useEmpleados.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useEmpleados = defineStore('empleados', {
|
||||||
|
state: () => ({ empleados: [] }),
|
||||||
|
actions: {
|
||||||
|
// placeholder para cargar/crear empleados
|
||||||
|
async fetchAll () {
|
||||||
|
// simulamos fetch
|
||||||
|
this.empleados = []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
5
ui/src/stores/usePlanillas.js
Normal file
5
ui/src/stores/usePlanillas.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const usePlanillas = defineStore('planillas', {
|
||||||
|
state: () => ({ planillas: [] }),
|
||||||
|
})
|
||||||
5
ui/src/stores/useTareas.js
Normal file
5
ui/src/stores/useTareas.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useTareas = defineStore('tareas', {
|
||||||
|
state: () => ({ tareas: [] }),
|
||||||
|
})
|
||||||
20
ui/src/stores/useUI.js
Normal file
20
ui/src/stores/useUI.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// src/stores/useUi.js
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useUi = defineStore('ui', {
|
||||||
|
state: () => ({
|
||||||
|
sidebarOpen: true, // visible por defecto en desktop
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
toggleSidebar () {
|
||||||
|
this.sidebarOpen = !this.sidebarOpen
|
||||||
|
},
|
||||||
|
closeSidebar () {
|
||||||
|
this.sidebarOpen = false
|
||||||
|
},
|
||||||
|
openSidebar () {
|
||||||
|
this.sidebarOpen = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,79 +1,4 @@
|
|||||||
:root {
|
@import "tailwindcss";
|
||||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
@tailwind base;
|
||||||
line-height: 1.5;
|
@tailwind components;
|
||||||
font-weight: 400;
|
@tailwind utilities;
|
||||||
|
|
||||||
color-scheme: light dark;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #646cff;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #535bf2;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 3.2em;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
padding: 0.6em 1.2em;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: inherit;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.25s;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
border-color: #646cff;
|
|
||||||
}
|
|
||||||
button:focus,
|
|
||||||
button:focus-visible {
|
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
:root {
|
|
||||||
color: #213547;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #747bff;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
14
ui/src/views/ChatView.vue
Normal file
14
ui/src/views/ChatView.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup>
|
||||||
|
/* Vista raíz “/” → muestra el chat estilo ChatGPT */
|
||||||
|
import CanvasChat from '@/components/chat/CanvasChat.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex flex-col">
|
||||||
|
<CanvasChat class="flex-1" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* nada por ahora */
|
||||||
|
</style>
|
||||||
21
ui/src/views/NotFound.vue
Normal file
21
ui/src/views/NotFound.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
const router = useRouter()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex flex-col items-center justify-center gap-6 p-8 text-center">
|
||||||
|
<h1 class="text-5xl font-bold text-teal-600">404</h1>
|
||||||
|
<p class="text-lg text-gray-700">Uy, no encontramos esa página.</p>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="router.push('/')"
|
||||||
|
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-teal-600 text-white hover:bg-teal-700 transition">
|
||||||
|
← Volver al inicio
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Estilos extra opcionales */
|
||||||
|
</style>
|
||||||
0
ui/src/views/SettingsView.vue
Normal file
0
ui/src/views/SettingsView.vue
Normal file
1
ui/src/views/asistencias/AsistenciaForm.vue
Normal file
1
ui/src/views/asistencias/AsistenciaForm.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
ui/src/views/asistencias/AsistenciasIndex.vue
Normal file
1
ui/src/views/asistencias/AsistenciasIndex.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
ui/src/views/empleados/EmpleadoForm.vue
Normal file
1
ui/src/views/empleados/EmpleadoForm.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
ui/src/views/empleados/EmpleadosIndex.vue
Normal file
1
ui/src/views/empleados/EmpleadosIndex.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
ui/src/views/planillas/PlanillaForm.vue
Normal file
1
ui/src/views/planillas/PlanillaForm.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
ui/src/views/planillas/PlanillasIndex.vue
Normal file
1
ui/src/views/planillas/PlanillasIndex.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
ui/src/views/tareas/TareaForm.vue
Normal file
1
ui/src/views/tareas/TareaForm.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
ui/src/views/tareas/TareasIndex.vue
Normal file
1
ui/src/views/tareas/TareasIndex.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,7 +1,17 @@
|
|||||||
|
// vite.config.js
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
tailwindcss(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src'), // ← apunta a /ui/src
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user