feat: Agregar visualizador de preview para templates
- Nuevo composable usePreview.ts para procesar operaciones en líneas de preview - Nuevo componente PaperSimulator.vue que simula el papel térmico - Nuevo modal PreviewModal.vue para vista previa con edición inline de variables - Botón "Vista previa" agregado a TemplateCard.vue - Integración del modal en TemplateList.vue
This commit is contained in:
228
app/components/preview/PreviewModal.vue
Normal file
228
app/components/preview/PreviewModal.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<script setup lang="ts">
|
||||
import type { PrintTemplate } from '~/composables/useTemplates'
|
||||
import type { Operation } from '~/composables/usePrintQueue'
|
||||
import { usePreview, CHAR_LIMITS } from '~/composables/usePreview'
|
||||
|
||||
const props = defineProps<{
|
||||
template: PrintTemplate | null
|
||||
modelValue: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
'print': [operations: Operation[], variables: Record<string, string>]
|
||||
}>()
|
||||
|
||||
const { previewState, variableValues, generatePreview, updateVariable, extractVariables } = usePreview()
|
||||
|
||||
// Estado local para edición de variables
|
||||
const editingVariable = ref<string | null>(null)
|
||||
const editingValue = ref('')
|
||||
const editInputRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
// Variables extraídas del template
|
||||
const variables = computed(() => {
|
||||
if (!props.template) return []
|
||||
return extractVariables(props.template.operations)
|
||||
})
|
||||
|
||||
// Inicializar preview cuando se abre el modal
|
||||
watch(() => props.modelValue, (isOpen) => {
|
||||
if (isOpen && props.template) {
|
||||
// Inicializar valores de variables con defaults
|
||||
const initialValues: Record<string, string> = {}
|
||||
for (const v of variables.value) {
|
||||
initialValues[v.name] = v.defaultValue || v.name
|
||||
}
|
||||
generatePreview(props.template.operations, initialValues)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// Manejar click en variable para editar
|
||||
function handleEditVariable(variableName: string, currentValue: string) {
|
||||
editingVariable.value = variableName
|
||||
editingValue.value = currentValue
|
||||
// Focus en el input después de renderizar
|
||||
nextTick(() => {
|
||||
editInputRef.value?.focus()
|
||||
editInputRef.value?.select()
|
||||
})
|
||||
}
|
||||
|
||||
// Confirmar edición de variable
|
||||
function confirmEdit() {
|
||||
if (editingVariable.value && props.template) {
|
||||
updateVariable(editingVariable.value, editingValue.value, props.template.operations)
|
||||
}
|
||||
editingVariable.value = null
|
||||
}
|
||||
|
||||
// Cancelar edición
|
||||
function cancelEdit() {
|
||||
editingVariable.value = null
|
||||
}
|
||||
|
||||
// Manejar teclas en el input
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
confirmEdit()
|
||||
} else if (event.key === 'Escape') {
|
||||
cancelEdit()
|
||||
}
|
||||
}
|
||||
|
||||
// Cerrar modal
|
||||
function closeModal() {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
// Imprimir
|
||||
function handlePrint() {
|
||||
if (props.template) {
|
||||
emit('print', props.template.operations, { ...variableValues.value })
|
||||
}
|
||||
closeModal()
|
||||
}
|
||||
|
||||
// Info del preview
|
||||
const previewInfo = computed(() => {
|
||||
if (!previewState.value) return null
|
||||
const limits = CHAR_LIMITS[previewState.value.font]
|
||||
return {
|
||||
font: previewState.value.font === 'font_a' ? 'Font A' : 'Font B',
|
||||
chars: limits.normal,
|
||||
lines: previewState.value.totalLines,
|
||||
warnings: previewState.value.warnings.length
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal
|
||||
:model-value="modelValue"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
:ui="{
|
||||
width: 'max-w-2xl'
|
||||
}"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-eye" class="text-primary-500" />
|
||||
<span class="font-medium">Vista Previa</span>
|
||||
<span v-if="template" class="text-gray-500">- {{ template.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div class="space-y-4">
|
||||
<!-- Editor de variable inline (aparece sobre el paper) -->
|
||||
<div
|
||||
v-if="editingVariable"
|
||||
class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-3"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm font-medium text-blue-700 dark:text-blue-300">
|
||||
{{ editingVariable }}:
|
||||
</label>
|
||||
<input
|
||||
ref="editInputRef"
|
||||
v-model="editingValue"
|
||||
type="text"
|
||||
class="flex-1 px-2 py-1 text-sm border border-blue-300 dark:border-blue-600 rounded bg-white dark:bg-gray-800 font-mono"
|
||||
@keydown="handleKeydown"
|
||||
@blur="confirmEdit"
|
||||
/>
|
||||
<UButton size="xs" color="primary" @click="confirmEdit">
|
||||
<UIcon name="i-heroicons-check" />
|
||||
</UButton>
|
||||
<UButton size="xs" variant="ghost" @click="cancelEdit">
|
||||
<UIcon name="i-heroicons-x-mark" />
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Simulador de papel -->
|
||||
<div class="flex justify-center">
|
||||
<PreviewPaperSimulator
|
||||
v-if="previewState"
|
||||
:lines="previewState.lines"
|
||||
:font="previewState.font"
|
||||
@edit-variable="handleEditVariable"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Barra de información -->
|
||||
<div
|
||||
v-if="previewInfo"
|
||||
class="flex items-center justify-center gap-4 text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
<UIcon name="i-heroicons-document-text" class="w-4 h-4" />
|
||||
{{ previewInfo.font }} ({{ previewInfo.chars }} chars)
|
||||
</span>
|
||||
<span class="text-gray-300 dark:text-gray-600">|</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<UIcon name="i-heroicons-bars-3" class="w-4 h-4" />
|
||||
{{ previewInfo.lines }} líneas
|
||||
</span>
|
||||
<template v-if="previewInfo.warnings > 0">
|
||||
<span class="text-gray-300 dark:text-gray-600">|</span>
|
||||
<span class="flex items-center gap-1 text-amber-500">
|
||||
<UIcon name="i-heroicons-exclamation-triangle" class="w-4 h-4" />
|
||||
{{ previewInfo.warnings }} {{ previewInfo.warnings === 1 ? 'advertencia' : 'advertencias' }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-gray-300 dark:text-gray-600">|</span>
|
||||
<span class="flex items-center gap-1 text-green-500">
|
||||
<UIcon name="i-heroicons-check-circle" class="w-4 h-4" />
|
||||
OK
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Lista de warnings -->
|
||||
<div
|
||||
v-if="previewState?.warnings.length"
|
||||
class="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-3"
|
||||
>
|
||||
<div class="flex items-start gap-2">
|
||||
<UIcon name="i-heroicons-exclamation-triangle" class="w-5 h-5 text-amber-500 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-amber-700 dark:text-amber-300">Advertencias:</p>
|
||||
<ul class="mt-1 space-y-1 text-amber-600 dark:text-amber-400">
|
||||
<li v-for="(warning, i) in previewState.warnings" :key="i">
|
||||
{{ warning }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tip de uso -->
|
||||
<p class="text-xs text-center text-gray-400 dark:text-gray-500">
|
||||
<UIcon name="i-heroicons-light-bulb" class="w-3 h-3 inline" />
|
||||
Click en los valores resaltados en azul para editarlos
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<UButton variant="ghost" @click="closeModal">
|
||||
Cancelar
|
||||
</UButton>
|
||||
<UButton
|
||||
color="primary"
|
||||
icon="i-heroicons-printer"
|
||||
@click="handlePrint"
|
||||
:disabled="!previewState || previewState.lines.length === 0"
|
||||
>
|
||||
Imprimir
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
Reference in New Issue
Block a user