refactor(ui): Rediseño completo de UI con Nuxt UI 4
- Nuevo layout responsivo mobile-first con tabs inferiores - Sidebar colapsable en desktop con cola de impresión - Sistema de templates reutilizables con localStorage - Soporte Dark/Light mode con UColorModeButton - Composables usePrintQueue y useTemplates para estado global - Componentes modulares: CommandBuilder, QuickActions, PrintQueue, QueueItem - Navegación por tabs: Constructor | Cola | Templates
This commit is contained in:
81
app/composables/usePrintQueue.ts
Normal file
81
app/composables/usePrintQueue.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
export interface Operation {
|
||||
op: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface PrintResult {
|
||||
ok: boolean
|
||||
msg?: string
|
||||
error?: string
|
||||
code?: string
|
||||
raw?: string
|
||||
}
|
||||
|
||||
export function usePrintQueue() {
|
||||
const operations = useState<Operation[]>('printQueue', () => [])
|
||||
const result = useState<PrintResult>('printResult', () => ({ ok: true, msg: 'Listo.' }))
|
||||
const loading = useState('printLoading', () => false)
|
||||
|
||||
function addOperations(ops: Operation[]) {
|
||||
if (Array.isArray(ops) && ops.length) {
|
||||
operations.value = [...operations.value, ...ops]
|
||||
}
|
||||
}
|
||||
|
||||
function updateOperation(index: number, op: Operation) {
|
||||
operations.value = operations.value.map((o, i) => i === index ? op : o)
|
||||
}
|
||||
|
||||
function removeOperation(index: number) {
|
||||
operations.value = operations.value.filter((_, i) => i !== index)
|
||||
}
|
||||
|
||||
function moveOperation(from: number, direction: 'up' | 'down') {
|
||||
const to = direction === 'up' ? from - 1 : from + 1
|
||||
if (to < 0 || to >= operations.value.length) return
|
||||
|
||||
const arr = [...operations.value]
|
||||
;[arr[from], arr[to]] = [arr[to], arr[from]]
|
||||
operations.value = arr
|
||||
}
|
||||
|
||||
function clearQueue() {
|
||||
operations.value = []
|
||||
}
|
||||
|
||||
function loadFromTemplate(ops: Operation[]) {
|
||||
operations.value = JSON.parse(JSON.stringify(ops))
|
||||
}
|
||||
|
||||
async function sendToPrinter() {
|
||||
if (operations.value.length === 0) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
result.value = await $fetch('/api/print', {
|
||||
method: 'POST',
|
||||
body: { operations: operations.value }
|
||||
})
|
||||
} catch (error: any) {
|
||||
result.value = {
|
||||
ok: false,
|
||||
error: error.message || 'Error al enviar a la impresora'
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
operations: readonly(operations),
|
||||
result: readonly(result),
|
||||
loading: readonly(loading),
|
||||
addOperations,
|
||||
updateOperation,
|
||||
removeOperation,
|
||||
moveOperation,
|
||||
clearQueue,
|
||||
loadFromTemplate,
|
||||
sendToPrinter
|
||||
}
|
||||
}
|
||||
87
app/composables/useTemplates.ts
Normal file
87
app/composables/useTemplates.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { Operation } from './usePrintQueue'
|
||||
|
||||
export interface PrintTemplate {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
operations: Operation[]
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'printercentral-templates'
|
||||
|
||||
export function useTemplates() {
|
||||
const templates = useState<PrintTemplate[]>('templates', () => [])
|
||||
const initialized = useState('templatesInitialized', () => false)
|
||||
|
||||
// Cargar de localStorage al iniciar (solo cliente)
|
||||
if (import.meta.client && !initialized.value) {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
if (stored) {
|
||||
try {
|
||||
templates.value = JSON.parse(stored)
|
||||
} catch (e) {
|
||||
console.error('Error parsing templates:', e)
|
||||
}
|
||||
}
|
||||
initialized.value = true
|
||||
}
|
||||
|
||||
// Guardar en localStorage cuando cambie
|
||||
watch(templates, (val) => {
|
||||
if (import.meta.client) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(val))
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
function saveTemplate(name: string, description: string, operations: Operation[]): PrintTemplate {
|
||||
const template: PrintTemplate = {
|
||||
id: crypto.randomUUID(),
|
||||
name,
|
||||
description,
|
||||
operations: JSON.parse(JSON.stringify(operations)),
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now()
|
||||
}
|
||||
templates.value = [...templates.value, template]
|
||||
return template
|
||||
}
|
||||
|
||||
function updateTemplate(id: string, updates: Partial<Omit<PrintTemplate, 'id' | 'createdAt'>>) {
|
||||
const idx = templates.value.findIndex(t => t.id === id)
|
||||
if (idx !== -1) {
|
||||
templates.value = templates.value.map((t, i) =>
|
||||
i === idx ? { ...t, ...updates, updatedAt: Date.now() } : t
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function deleteTemplate(id: string) {
|
||||
templates.value = templates.value.filter(t => t.id !== id)
|
||||
}
|
||||
|
||||
function loadTemplate(id: string): Operation[] | null {
|
||||
const template = templates.value.find(t => t.id === id)
|
||||
return template ? JSON.parse(JSON.stringify(template.operations)) : null
|
||||
}
|
||||
|
||||
function duplicateTemplate(id: string): PrintTemplate | null {
|
||||
const template = templates.value.find(t => t.id === id)
|
||||
if (!template) return null
|
||||
return saveTemplate(
|
||||
`${template.name} (copia)`,
|
||||
template.description || '',
|
||||
template.operations
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
templates: readonly(templates),
|
||||
saveTemplate,
|
||||
updateTemplate,
|
||||
deleteTemplate,
|
||||
loadTemplate,
|
||||
duplicateTemplate
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user