Major additions accumulated over 9 days — single commit per request. Flow editor (new): - Generic visual editor for step trees, usable by project wizard + agent flows - PROJECT_KINDS / AGENT_KINDS catalogs decouple UI from domain - Drag-and-drop reorder via vuedraggable with scope isolation per peer group - Chain-aware depends_on rewrite on reorder (sequential only — DAGs preserved) - Variable picker with per-applies_to catalog (Customer / Quotation / Service Contract / Issue / Subscription), insert + copy-clipboard modes - trigger_condition helper with domain-specific JSONLogic examples - Global FlowEditorDialog mounted once in MainLayout, Odoo inline pattern - Server: targo-hub flow-runtime.js, flow-api.js, flow-templates.js - ERPNext: Flow Template/Run doctypes, scheduler, 5 seeded system templates - depends_on chips resolve to step labels instead of opaque "s4" ids QR/OCR scanner (field app): - Camera capture → Gemini Vision via targo-hub with 8s timeout - IndexedDB offline queue retries photos when signal returns - Watcher merges late-arriving scan results into the live UI Dispatch: - Planning mode (draft → publish) with offer pool for unassigned jobs - Shared presets, recurrence selector, suggested-slots dialog - PublishScheduleModal, unassign confirmation Ops app: - ClientDetailPage composables extraction (useClientData, useDeviceStatus, useWifiDiagnostic, useModemDiagnostic) - Project wizard: shared detail sections, wizard catalog/publish composables - Address pricing composable + pricing-mock data - Settings redesign hosting flow templates Targo-hub: - Contract acceptance (JWT residential + DocuSeal commercial tracks) - Referral system - Modem-bridge diagnostic normalizer - Device extractors consolidated Migration scripts: - Invoice/quote print format setup, Jinja rendering - Additional import + fix scripts (reversals, dates, customers, payments) Docs: - Consolidated: old scattered MDs → HANDOFF, ARCHITECTURE, DATA_AND_FLOWS, FLOW_EDITOR_ARCHITECTURE, BILLING_AND_PAYMENTS, CPE_MANAGEMENT, APP_DESIGN_GUIDELINES - Archived legacy wizard PHP for reference - STATUS snapshots for 2026-04-18/19 Cleanup: - Removed ~40 generated PDFs/HTMLs (invoice_preview*, rendered_jinja*) - .gitignore now covers invoice preview output + nested .DS_Store Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
77 lines
2.2 KiB
Vue
77 lines
2.2 KiB
Vue
<!--
|
|
FieldInput.vue — Generic field renderer driven by a field descriptor.
|
|
|
|
Renders the right Quasar input based on `field.type`. Adding a new field
|
|
type = adding a branch here. Used by StepEditorModal.
|
|
|
|
Props:
|
|
- field: { name, type, label, required, options, placeholder, help, rows }
|
|
- modelValue: current value (v-model)
|
|
|
|
Events:
|
|
- update:modelValue(newValue)
|
|
|
|
Supported types:
|
|
- 'text' → q-input type=text
|
|
- 'textarea' → q-input type=textarea
|
|
- 'number' → q-input type=number
|
|
- 'select' → q-select with options
|
|
- 'datetime' → q-input type=datetime-local
|
|
- 'json' → q-input textarea with JSON.parse/stringify on blur
|
|
- 'webhook' → q-input with URL validation
|
|
-->
|
|
<template>
|
|
<component :is="inputComponent" v-bind="inputProps" />
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, h } from 'vue'
|
|
import { QInput, QSelect } from 'quasar'
|
|
|
|
const props = defineProps({
|
|
field: { type: Object, required: true },
|
|
modelValue: { default: null },
|
|
})
|
|
|
|
const emit = defineEmits(['update:modelValue'])
|
|
|
|
/** Pick the Quasar component based on field.type. */
|
|
const inputComponent = computed(() => {
|
|
return props.field.type === 'select' ? QSelect : QInput
|
|
})
|
|
|
|
/** Props passed to the chosen component, computed once per render. */
|
|
const inputProps = computed(() => {
|
|
const f = props.field
|
|
const base = {
|
|
'model-value': props.modelValue ?? '',
|
|
'onUpdate:modelValue': (v) => emit('update:modelValue', v),
|
|
label: f.label + (f.required ? ' *' : ''),
|
|
'stack-label': true,
|
|
dense: true,
|
|
outlined: true,
|
|
hint: f.help,
|
|
placeholder: f.placeholder,
|
|
}
|
|
|
|
if (f.type === 'select') {
|
|
return {
|
|
...base,
|
|
options: (f.options || []).map(o => typeof o === 'string' ? { label: o, value: o } : o),
|
|
'emit-value': true,
|
|
'map-options': true,
|
|
}
|
|
}
|
|
|
|
if (f.type === 'textarea' || f.type === 'json') {
|
|
return { ...base, type: 'textarea', rows: f.rows || 3, autogrow: false }
|
|
}
|
|
|
|
if (f.type === 'number') return { ...base, type: 'number' }
|
|
if (f.type === 'datetime') return { ...base, type: 'datetime-local' }
|
|
|
|
// 'text' | 'webhook' | default
|
|
return { ...base, type: 'text' }
|
|
})
|
|
</script>
|