feat: Variables programáticas en templates

Permite definir variables en templates con sintaxis {{nombre🏷️default}}
- Auto-detección de variables al guardar templates
- Drawer para completar valores al cargar template con variables
- Badge mostrando cantidad de variables en tarjeta de template
- Resolución de variables antes de cargar en cola
This commit is contained in:
2025-11-25 01:57:24 -06:00
parent 05bf0e3c91
commit c7c32d8c54
6 changed files with 214 additions and 13 deletions

View File

@@ -51,10 +51,13 @@ function formatDate(date: string | number) {
>
{{ template.description }}
</p>
<div class="flex items-center gap-2 mt-2">
<div class="flex items-center gap-2 mt-2 flex-wrap">
<UBadge variant="subtle" size="xs">
{{ template.operations.length }} comandos
</UBadge>
<UBadge v-if="template.variables?.length" variant="subtle" size="xs" color="primary">
{{ template.variables.length }} {{ template.variables.length === 1 ? 'variable' : 'variables' }}
</UBadge>
<span class="text-xs text-gray-400 dark:text-gray-500">
{{ formatDate(template.updatedAt) }}
</span>

View File

@@ -1,21 +1,48 @@
<script setup lang="ts">
import type { PrintTemplate } from '~/composables/useTemplates'
import { resolveVariables } from '~/composables/useTemplates'
const templates = useTemplates()
const queue = usePrintQueue()
const toast = useToast()
// Estado para el drawer de variables
const variablesDrawerOpen = ref(false)
const selectedTemplate = ref<PrintTemplate | null>(null)
// Cargar templates al montar
onMounted(() => {
templates.fetchTemplates()
})
function loadTemplate(id: string) {
const ops = templates.loadTemplate(id)
if (ops) {
queue.loadFromTemplate(ops)
toast.add({ title: 'Template cargado en la cola', color: 'success' })
const template = templates.templates.value.find(t => t.id === id)
if (!template) return
// Si tiene variables, abrir el drawer
if (template.variables && template.variables.length > 0) {
selectedTemplate.value = template
variablesDrawerOpen.value = true
} else {
// Sin variables, cargar directo
const ops = templates.loadTemplate(id)
if (ops) {
queue.loadFromTemplate(ops)
toast.add({ title: 'Template cargado en la cola', color: 'success' })
}
}
}
function handleLoadWithVariables(values: Record<string, string>) {
if (!selectedTemplate.value) return
const ops = resolveVariables(selectedTemplate.value.operations, values)
queue.loadFromTemplate(ops)
toast.add({ title: 'Template cargado en la cola', color: 'success' })
variablesDrawerOpen.value = false
selectedTemplate.value = null
}
async function duplicateTemplate(id: string) {
const result = await templates.duplicateTemplate(id)
if (result) {
@@ -72,5 +99,13 @@ async function deleteTemplate(id: string) {
@delete="deleteTemplate"
/>
</div>
<!-- Drawer para completar variables -->
<TemplatesVariablesDrawer
:template="selectedTemplate"
:open="variablesDrawerOpen"
@update:open="variablesDrawerOpen = $event"
@load="handleLoadWithVariables"
/>
</div>
</template>

View File

@@ -0,0 +1,88 @@
<script setup lang="ts">
import type { PrintTemplate } from '~/composables/useTemplates'
const props = defineProps<{
template: PrintTemplate | null
open: boolean
}>()
const emit = defineEmits<{
'update:open': [value: boolean]
load: [values: Record<string, string>]
}>()
const values = ref<Record<string, string>>({})
// Inicializar con valores por defecto cuando cambia el template
watch(() => props.template, (t) => {
if (t) {
values.value = {}
for (const v of t.variables || []) {
values.value[v.name] = v.defaultValue || ''
}
}
}, { immediate: true })
function handleLoad() {
emit('load', { ...values.value })
}
function handleDrawerUpdate(value: boolean) {
emit('update:open', value)
}
function handleClose() {
emit('update:open', false)
}
// Validar que todos los campos requeridos estén completos
const canLoad = computed(() => {
if (!props.template?.variables) return false
return props.template.variables.every(v => values.value[v.name]?.trim())
})
</script>
<template>
<UDrawer
:open="open"
direction="bottom"
title="Completar variables"
description="Ingresa los valores para las variables del template"
@update:open="handleDrawerUpdate"
>
<template #body>
<div class="space-y-4 p-4">
<p class="text-sm text-gray-500 dark:text-gray-400">
Template: <strong>{{ template?.name }}</strong>
</p>
<UFormField
v-for="v in template?.variables || []"
:key="v.name"
:label="v.label || v.name"
required
>
<UInput
v-model="values[v.name]"
:placeholder="v.defaultValue || `Ingrese ${v.label || v.name}`"
/>
</UFormField>
</div>
</template>
<template #footer>
<div class="flex gap-2 justify-end p-4">
<UButton variant="ghost" @click="handleClose">
Cancelar
</UButton>
<UButton
color="primary"
:disabled="!canLoad"
@click="handleLoad"
>
Cargar en cola
</UButton>
</div>
</template>
</UDrawer>
</template>