@asteby/metacore-runtime-react 49.7.0 → 49.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/business/document-editor-model.d.ts +27 -2
- package/dist/business/document-editor-model.d.ts.map +1 -1
- package/dist/business/document-editor-model.js +53 -3
- package/dist/business/document-editor.d.ts +7 -2
- package/dist/business/document-editor.d.ts.map +1 -1
- package/dist/business/document-editor.js +116 -21
- package/dist/business/document-lines.d.ts +21 -1
- package/dist/business/document-lines.d.ts.map +1 -1
- package/dist/business/document-lines.js +69 -7
- package/dist/business/line-items-editor.d.ts +21 -1
- package/dist/business/line-items-editor.d.ts.map +1 -1
- package/dist/business/line-items-editor.js +74 -15
- package/dist/business/line-items.d.ts +28 -0
- package/dist/business/line-items.d.ts.map +1 -1
- package/dist/business/line-items.js +1 -0
- package/dist/business/line-product-cell.d.ts +6 -0
- package/dist/business/line-product-cell.d.ts.map +1 -1
- package/dist/business/line-product-cell.js +9 -3
- package/dist/business/product-options.js +1 -1
- package/dist/dialogs/dynamic-record.js +1 -1
- package/dist/field-grid.d.ts +4 -3
- package/dist/field-grid.d.ts.map +1 -1
- package/dist/field-grid.js +1 -1
- package/dist/record-picker.d.ts +2 -0
- package/dist/record-picker.d.ts.map +1 -1
- package/dist/record-picker.js +11 -4
- package/package.json +1 -1
- package/src/__tests__/document-editor-inline-selects.test.tsx +11 -4
- package/src/__tests__/line-row-create-edit.test.tsx +312 -0
- package/src/business/document-editor-model.ts +60 -2
- package/src/business/document-editor.tsx +132 -17
- package/src/business/document-lines.ts +79 -7
- package/src/business/line-items-editor.tsx +158 -30
- package/src/business/line-items.ts +31 -0
- package/src/business/line-product-cell.tsx +32 -5
- package/src/business/product-options.tsx +1 -1
- package/src/dialogs/dynamic-record.tsx +1 -1
- package/src/field-grid.tsx +5 -4
- package/src/record-picker.tsx +16 -3
|
@@ -4,15 +4,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
4
4
|
// Controlado: `value`/`onChange` con `LineItem[]`. Guardar = `serializeLineItems`.
|
|
5
5
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
|
-
import { Heading, Minus, Plus, StickyNote, Trash2 } from 'lucide-react';
|
|
7
|
+
import { Heading, Minus, Pencil, Plus, StickyNote, Trash2 } from 'lucide-react';
|
|
8
8
|
import { Button, Input } from '@asteby/metacore-ui';
|
|
9
9
|
import { useCan } from '../permissions-context';
|
|
10
10
|
import { useFormatter } from './format';
|
|
11
|
-
import { addProductLine, applyProductToLine, lineGridKeyCommand, } from './document-lines';
|
|
11
|
+
import { addProductLine, applyCatalogPending, applyProductToLine, refreshLineFromProduct, lineGridKeyCommand, } from './document-lines';
|
|
12
12
|
import { computeLine, computeTotals, makeLine, validateLineItems, } from './line-items';
|
|
13
13
|
import { LineProductCell } from './line-product-cell';
|
|
14
14
|
const num = (s) => (s.trim() === '' ? 0 : Number(s.replace(',', '.')) || 0);
|
|
15
|
-
export function DocumentLinesGrid({ value, onChange, columns = ['discount'], policy, serverErrors, onValidate, onRequestProduct, search, priceSource = 'sale', mode = 'free', discountMode = 'percent', quantityStepper, mergeSameProduct = true, warehouseId, allowSections = true, readOnly = false, editPermission, currency, showTotals = true, }) {
|
|
15
|
+
export function DocumentLinesGrid({ value, onChange, columns = ['discount'], policy, serverErrors, onValidate, onRequestProduct, search, priceSource = 'sale', mode = 'free', discountMode = 'percent', quantityStepper, mergeSameProduct = true, warehouseId, allowSections = true, readOnly = false, editPermission, currency, showTotals = true, onCreateProduct, onEditProduct, productLabel, unitLabel, }) {
|
|
16
16
|
const { t } = useTranslation();
|
|
17
17
|
const can = useCan();
|
|
18
18
|
const fmt = useFormatter({ currency });
|
|
@@ -29,6 +29,11 @@ export function DocumentLinesGrid({ value, onChange, columns = ['discount'], pol
|
|
|
29
29
|
const stepper = quantityStepper ?? !!search;
|
|
30
30
|
const canAdd = !locked && !fromSource;
|
|
31
31
|
const [undo, setUndo] = useState(null);
|
|
32
|
+
// Crear/editar desde la celda resuelven DESPUÉS de un modal: se trabaja
|
|
33
|
+
// sobre los renglones de ese momento (no los del render que abrió el modal).
|
|
34
|
+
const latest = useRef(value);
|
|
35
|
+
latest.current = value;
|
|
36
|
+
const entity = productLabel ?? t('lineItems.productEntity', { defaultValue: 'producto' });
|
|
32
37
|
const emit = (next) => {
|
|
33
38
|
onChange(next);
|
|
34
39
|
const priced = discountMode === 'amount' ? next.map((l) => (l.kind === 'item' ? { ...l, discount_kind: 'amount' } : l)) : next;
|
|
@@ -37,7 +42,7 @@ export function DocumentLinesGrid({ value, onChange, columns = ['discount'], pol
|
|
|
37
42
|
const patch = (idx, p) => {
|
|
38
43
|
if (discountMode === 'amount')
|
|
39
44
|
p = { ...p, discount_kind: 'amount' };
|
|
40
|
-
emit(value.map((l, i) => (i === idx ? { ...l, ...p } : l)));
|
|
45
|
+
emit(value.map((l, i) => (i === idx ? withoutAnsweredPending({ ...l, ...p }, p) : l)));
|
|
41
46
|
};
|
|
42
47
|
const remove = (idx) => {
|
|
43
48
|
setUndo(value);
|
|
@@ -61,23 +66,55 @@ export function DocumentLinesGrid({ value, onChange, columns = ['discount'], pol
|
|
|
61
66
|
}, [focusQty, value]);
|
|
62
67
|
const blankLine = () => makeLine(discountMode === 'amount' ? { discount_kind: 'amount' } : {});
|
|
63
68
|
/** Renglón vacío del final: el producto entra como renglón nuevo (o suma al mismo). */
|
|
64
|
-
const pickProduct = (product, variant) => {
|
|
65
|
-
const next = addProductLine(
|
|
69
|
+
const pickProduct = (product, variant, base = value) => {
|
|
70
|
+
const next = addProductLine(base, product, variant, { priceSource, warehouseId, mergeSameProduct });
|
|
66
71
|
emit(next);
|
|
67
72
|
const id = variant?.id ?? product.id;
|
|
68
|
-
const target = next.find((l) => l.kind === 'item' && l.product_id === id && !
|
|
73
|
+
const target = next.find((l) => l.kind === 'item' && l.product_id === id && !base.includes(l)) ?? next[next.length - 1];
|
|
69
74
|
if (target)
|
|
70
75
|
setFocusQty(target.key);
|
|
71
76
|
};
|
|
72
77
|
/** Renglón libre existente: el producto elegido en su celda llena ESE renglón. */
|
|
73
|
-
const fillLine = (idx, product, variant) => {
|
|
74
|
-
const cur =
|
|
78
|
+
const fillLine = (idx, product, variant, base = value) => {
|
|
79
|
+
const cur = base[idx];
|
|
75
80
|
if (!cur)
|
|
76
81
|
return;
|
|
77
82
|
const filled = applyProductToLine(cur, product, variant, { priceSource, warehouseId });
|
|
78
|
-
emit(
|
|
83
|
+
emit(base.map((l, i) => (i === idx ? (discountMode === 'amount' ? { ...filled, discount_kind: 'amount' } : filled) : l)));
|
|
79
84
|
setFocusQty(cur.key);
|
|
80
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* «Crear producto «texto»» / «+»: el alta del modelo de catálogo; al
|
|
88
|
+
* guardarse, el producto nuevo entra en ESE renglón (o en uno nuevo desde
|
|
89
|
+
* el renglón del final) y lo llena igual que al elegir uno existente.
|
|
90
|
+
*/
|
|
91
|
+
const createProduct = onCreateProduct
|
|
92
|
+
? async (query, lineKey) => {
|
|
93
|
+
const product = await onCreateProduct(query);
|
|
94
|
+
if (!product)
|
|
95
|
+
return;
|
|
96
|
+
const base = latest.current;
|
|
97
|
+
const idx = lineKey ? base.findIndex((l) => l.key === lineKey) : -1;
|
|
98
|
+
if (idx >= 0 && !base[idx].product_id)
|
|
99
|
+
fillLine(idx, product, undefined, base);
|
|
100
|
+
else
|
|
101
|
+
pickProduct(product, undefined, base);
|
|
102
|
+
}
|
|
103
|
+
: undefined;
|
|
104
|
+
/**
|
|
105
|
+
* Lápiz: edita el producto del renglón; al guardar, el renglón toma los
|
|
106
|
+
* valores nuevos salvo lo que el usuario sobrescribió (queda el aviso).
|
|
107
|
+
*/
|
|
108
|
+
const editProduct = onEditProduct
|
|
109
|
+
? async (line) => {
|
|
110
|
+
const product = await onEditProduct(line);
|
|
111
|
+
if (!product)
|
|
112
|
+
return;
|
|
113
|
+
const base = latest.current;
|
|
114
|
+
emit(base.map((l) => (l.key === line.key ? refreshLineFromProduct(l, product, { priceSource, warehouseId }) : l)));
|
|
115
|
+
}
|
|
116
|
+
: undefined;
|
|
117
|
+
const unitText = (u) => (!u ? undefined : unitLabel ? unitLabel(u) || undefined : u);
|
|
81
118
|
/** Texto escrito en el renglón vacío sin elegir producto → renglón libre con esa descripción. */
|
|
82
119
|
const addFreeFromDraft = (description) => {
|
|
83
120
|
const line = { ...blankLine(), description };
|
|
@@ -129,7 +166,12 @@ export function DocumentLinesGrid({ value, onChange, columns = ['discount'], pol
|
|
|
129
166
|
const a = computeLine(shown);
|
|
130
167
|
const kind = shown.discount_kind ?? 'percent';
|
|
131
168
|
const qtyMax = l.max_quantity;
|
|
132
|
-
return (_jsxs("tr", { className: "group border-t align-top transition-colors hover:bg-muted/30 focus-within:bg-muted/20", "data-line-key": l.key, children: [_jsxs("td", { className: CELL, children: [search && canAdd && !l.product_id ? (_jsx(LineProductCell, { search: search, text: l.description, onTextChange: (text) => patch(i, { description: text }), onPick: (p, v) => fillLine(i, p, v), onKeyDownClosed: (e) => descKeys(e, i, l), warehouseId: warehouseId, currency: currency, ariaLabel: t('lineItems.description', { defaultValue: 'Descripción' }), invalid: !!cell(i, 'description'), dataCell: "description" })) : (_jsx(Input, { value: l.description, disabled: locked, className: INPUT, "data-cell": "description", "aria-label": t('lineItems.description', { defaultValue: 'Descripción' }), "aria-invalid": !!cell(i, 'description'), onChange: (e) => patch(i, { description: e.target.value }), onKeyDown: (e) => descKeys(e, i, l) })
|
|
169
|
+
return (_jsxs("tr", { className: "group border-t align-top transition-colors hover:bg-muted/30 focus-within:bg-muted/20", "data-line-key": l.key, children: [_jsxs("td", { className: CELL, children: [search && canAdd && !l.product_id ? (_jsx(LineProductCell, { search: search, text: l.description, onTextChange: (text) => patch(i, { description: text }), onPick: (p, v) => fillLine(i, p, v), onKeyDownClosed: (e) => descKeys(e, i, l), warehouseId: warehouseId, currency: currency, ariaLabel: t('lineItems.description', { defaultValue: 'Descripción' }), invalid: !!cell(i, 'description'), dataCell: "description", onCreate: createProduct ? (q) => void createProduct(q, l.key) : undefined, entityLabel: entity })) : (_jsxs("div", { className: "relative", "data-slot": "line-description", children: [_jsx(Input, { value: l.description, disabled: locked, className: INPUT + (editProduct && l.product_id && !locked ? ' pr-9' : ''), "data-cell": "description", "aria-label": t('lineItems.description', { defaultValue: 'Descripción' }), "aria-invalid": !!cell(i, 'description'), onChange: (e) => patch(i, { description: e.target.value }), onKeyDown: (e) => descKeys(e, i, l) }), editProduct && l.product_id && !locked && (_jsx(Button, { type: "button", size: "icon", variant: "ghost", className: EDIT, "data-slot": "line-edit-product", "aria-label": t('lineItems.editProduct', { defaultValue: 'Editar {{entity}}', entity }), title: t('lineItems.editProduct', { defaultValue: 'Editar {{entity}}', entity }), onClick: () => void editProduct(l), children: _jsx(Pencil, { className: "size-3.5" }) }))] })), (l.sku || unitText(l.unit)) && (_jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [l.sku, !show('unit') ? unitText(l.unit) : ''].filter(Boolean).join(' · ') })), l.catalog_pending && !locked && (_jsxs("p", { className: "mt-0.5 text-xs text-muted-foreground", "data-slot": "catalog-pending", role: "status", children: [l.catalog_pending.unit_price != null
|
|
170
|
+
? t('lineItems.catalogPriceChanged', {
|
|
171
|
+
defaultValue: 'Precio de catálogo cambió a {{price}}',
|
|
172
|
+
price: fmt.money(l.catalog_pending.unit_price),
|
|
173
|
+
})
|
|
174
|
+
: t('lineItems.catalogChanged', { defaultValue: 'El producto cambió en el catálogo' }), ' — ', _jsx("button", { type: "button", className: "font-medium text-foreground underline-offset-2 hover:underline focus-visible:underline focus-visible:outline-none", onClick: () => emit(latest.current.map((x) => (x.key === l.key ? applyCatalogPending(x) : x))), children: t('lineItems.catalogApply', { defaultValue: 'aplicar' }) })] })), cell(i, 'description') && _jsx("p", { className: "mt-0.5 text-xs text-destructive", children: cell(i, 'description') })] }), show('unit') && (_jsx("td", { className: CELL, children: _jsx(Input, { className: INPUT, value: l.unit ?? '', disabled: locked, onChange: (e) => patch(i, { unit: e.target.value }) }) })), _jsxs("td", { className: CELL, children: [stepper ? (_jsxs("div", { className: "flex items-center justify-end gap-1", children: [_jsx(Button, { type: "button", size: "icon", variant: "outline", className: "size-7", disabled: locked || toNum(l.quantity) <= (fromSource ? 0 : 1), "aria-label": t('lineItems.decreaseQty', { defaultValue: 'Disminuir cantidad' }), onClick: () => patch(i, { quantity: Math.max(fromSource ? 0 : 1, toNum(l.quantity) - 1) }), children: _jsx(Minus, { className: "size-3" }) }), _jsx(Input, { inputMode: "decimal", className: "h-8 w-14 text-right tabular-nums", "data-cell": "quantity", value: String(l.quantity), disabled: locked, "aria-label": t('lineItems.quantity', { defaultValue: 'Cant.' }), "aria-invalid": !!cell(i, 'quantity'), onChange: (e) => {
|
|
133
175
|
let q = num(e.target.value);
|
|
134
176
|
if (qtyMax != null && q > qtyMax)
|
|
135
177
|
q = qtyMax;
|
|
@@ -140,11 +182,21 @@ export function DocumentLinesGrid({ value, onChange, columns = ['discount'], pol
|
|
|
140
182
|
const next = toNum(l.quantity) + 1;
|
|
141
183
|
patch(i, { quantity: qtyMax != null ? Math.min(next, qtyMax) : next });
|
|
142
184
|
}, children: _jsx(Plus, { className: "size-3" }) })] })) : (_jsx(Input, { inputMode: "decimal", className: NUM, "data-cell": "quantity", value: String(l.quantity), disabled: locked, "aria-label": t('lineItems.quantity', { defaultValue: 'Cant.' }), "aria-invalid": !!cell(i, 'quantity'), onChange: (e) => patch(i, { quantity: num(e.target.value) }) })), cell(i, 'quantity') && _jsx("p", { className: "mt-0.5 text-xs text-destructive", children: cell(i, 'quantity') }), !cell(i, 'quantity') && warn(i, 'quantity') && _jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: warn(i, 'quantity') })] }), _jsxs("td", { className: CELL, children: [_jsx(Input, { inputMode: "decimal", className: NUM, value: String(l.unit_price), disabled: locked, "aria-label": priceSource === 'cost' ? t('lineItems.unitCost', { defaultValue: 'Costo unitario' }) : t('lineItems.unitPrice', { defaultValue: 'Precio' }), "aria-invalid": !!cell(i, 'unit_price'), onChange: (e) => patch(i, { unit_price: num(e.target.value) }) }), l.catalog_price != null && (_jsxs("p", { className: "mt-0.5 truncate whitespace-nowrap text-right text-xs text-muted-foreground", children: [t('lineItems.catalogPrice', { defaultValue: 'Catálogo' }), " ", fmt.money(l.catalog_price)] })), cell(i, 'unit_price') && _jsx("p", { className: "mt-0.5 text-xs text-destructive", children: cell(i, 'unit_price') }), !cell(i, 'unit_price') && warn(i, 'unit_price') && _jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: warn(i, 'unit_price') })] }), show('discount') && (_jsxs("td", { className: CELL, children: [_jsxs("div", { className: "flex items-center gap-1", children: [discountMode === 'both' && (_jsx(Button, { type: "button", size: "sm", variant: "ghost", className: "h-8 px-1.5", "aria-label": t('lineItems.discountKind', { defaultValue: 'Tipo de descuento' }), onClick: () => patch(i, { discount_kind: kind === 'amount' ? 'percent' : 'amount', discount: 0 }), children: kind === 'amount' ? '$' : '%' })), _jsx(Input, { inputMode: "decimal", className: NUM, value: String(l.discount), disabled: locked, "aria-invalid": !!cell(i, 'discount'), onChange: (e) => patch(i, { discount: num(e.target.value), discount_kind: kind }) })] }), cell(i, 'discount') && _jsx("p", { className: "mt-0.5 text-xs text-destructive", children: cell(i, 'discount') })] })), show('tax') && (_jsx("td", { className: CELL, children: _jsx(Input, { inputMode: "decimal", className: NUM, value: String(Math.round(l.tax_rate * 10000) / 100), disabled: locked, onChange: (e) => patch(i, { tax_rate: num(e.target.value) / 100 }) }) })), show('technician') && (_jsx("td", { className: CELL, children: _jsx(Input, { className: INPUT, value: l.technician_id ?? '', disabled: locked, onChange: (e) => patch(i, { technician_id: e.target.value }) }) })), show('lot') && (_jsx("td", { className: CELL, children: _jsx(Input, { className: INPUT, value: l.lot ?? '', disabled: locked, onChange: (e) => patch(i, { lot: e.target.value }) }) })), show('dot') && (_jsx("td", { className: CELL, children: _jsx(Input, { className: INPUT, value: l.dot ?? '', disabled: locked, onChange: (e) => patch(i, { dot: e.target.value }) }) })), _jsx("td", { className: "px-3 py-1.5 text-right align-top font-medium tabular-nums", "data-slot": "line-amount", children: _jsx("span", { className: "inline-flex h-8 items-center", children: fmt.money(a.net) }) }), _jsx("td", { className: "px-1 py-1.5 align-top", children: canAdd && (_jsx(Button, { type: "button", size: "icon", variant: "ghost", className: DELETE, onClick: () => remove(i), "aria-label": t('common.delete', { defaultValue: 'Eliminar' }), children: _jsx(Trash2, { className: "size-4" }) })) })] }, l.key));
|
|
143
|
-
}), value.length === 0 && !search && (_jsx("tr", { children: _jsx("td", { colSpan: 99, className: "px-3 py-6 text-center text-muted-foreground", children: t('lineItems.empty', { defaultValue: 'Aún no hay renglones. Agrega un producto o un renglón libre.' }) }) })), canAdd && search && (_jsx(DraftRow, { search: search, warehouseId: warehouseId, currency: currency, onPick: pickProduct, onFree: addFreeFromDraft, onAddBlank: () => emit([...value, blankLine()]),
|
|
185
|
+
}), value.length === 0 && !search && (_jsx("tr", { children: _jsx("td", { colSpan: 99, className: "px-3 py-6 text-center text-muted-foreground", children: t('lineItems.empty', { defaultValue: 'Aún no hay renglones. Agrega un producto o un renglón libre.' }) }) })), canAdd && search && (_jsx(DraftRow, { search: search, warehouseId: warehouseId, currency: currency, onPick: (p, v) => pickProduct(p, v), onFree: addFreeFromDraft, onAddBlank: () => emit([...value, blankLine()]), onCreate: createProduct ? (q) => void createProduct(q) : undefined, entityLabel: entity, span: colCount - 2 }))] }), showTotals && (_jsxs("tfoot", { className: "border-t bg-muted/20 text-sm tabular-nums", "data-slot": "line-totals", children: [_jsx(TotalRow, { span: colCount - 2, label: t('lineItems.subtotal', { defaultValue: 'Subtotal' }), value: fmt.money(totals.subtotal) }), totals.discount > 0 && _jsx(TotalRow, { span: colCount - 2, label: t('lineItems.discountTotal', { defaultValue: 'Descuento' }), value: `-${fmt.money(totals.discount)}` }), totals.tax > 0 && _jsx(TotalRow, { span: colCount - 2, label: t('lineItems.taxTotal', { defaultValue: 'Impuestos' }), value: fmt.money(totals.tax), slot: "tax-total" }), _jsx(TotalRow, { span: colCount - 2, label: t('lineItems.total', { defaultValue: 'Total' }), value: fmt.money(totals.total), slot: "grand-total", strong: true })] }))] }) }), validation.form && value.length > 0 && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: validation.form }), undo && canAdd && (_jsxs("p", { className: "text-sm text-muted-foreground", children: [t('lineItems.removed', { defaultValue: 'Renglón eliminado.' }), ' ', _jsx("button", { type: "button", className: "underline", onClick: () => {
|
|
144
186
|
emit(undo);
|
|
145
187
|
setUndo(null);
|
|
146
188
|
}, children: t('lineItems.undo', { defaultValue: 'Deshacer' }) })] })), _jsx("div", { className: "flex flex-wrap items-center gap-3", children: canAdd ? (_jsxs("div", { className: "flex flex-wrap gap-1", children: [onRequestProduct && !search && (_jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: onRequestProduct, children: [_jsx(Plus, { className: "mr-1 size-4" }), t('lineItems.addProduct', { defaultValue: 'Agregar producto' })] })), _jsxs(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => emit([...value, blankLine()]), children: [_jsx(Plus, { className: "mr-1 size-4" }), t('lineItems.addLine', { defaultValue: 'Renglón libre' })] }), allowSections && (_jsxs(_Fragment, { children: [_jsxs(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => emit([...value, makeLine({ kind: 'section' })]), children: [_jsx(Heading, { className: "mr-1 size-4" }), t('lineItems.addSection', { defaultValue: 'Sección' })] }), _jsxs(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => emit([...value, makeLine({ kind: 'note' })]), children: [_jsx(StickyNote, { className: "mr-1 size-4" }), t('lineItems.addNote', { defaultValue: 'Nota' })] })] }))] })) : null })] }));
|
|
147
189
|
}
|
|
190
|
+
/** Capturar a mano un campo con aviso de catálogo responde el aviso: deja de ofrecer «aplicar» para ese campo. */
|
|
191
|
+
function withoutAnsweredPending(line, p) {
|
|
192
|
+
if (!line.catalog_pending)
|
|
193
|
+
return line;
|
|
194
|
+
const pending = { ...line.catalog_pending };
|
|
195
|
+
for (const k of Object.keys(p))
|
|
196
|
+
delete pending[k];
|
|
197
|
+
const { catalog_pending: _drop, ...rest } = line;
|
|
198
|
+
return Object.keys(pending).length > 0 ? { ...rest, catalog_pending: pending } : rest;
|
|
199
|
+
}
|
|
148
200
|
/** Mismo componente. El nombre anterior sigue exportado. */
|
|
149
201
|
export const LineItemsEditor = DocumentLinesGrid;
|
|
150
202
|
function toNum(n) {
|
|
@@ -155,10 +207,10 @@ function toNum(n) {
|
|
|
155
207
|
* celda «Descripción» (no en una barra a todo lo ancho). Elegir agrega el
|
|
156
208
|
* producto; Enter con texto sin coincidencias lo agrega como renglón libre.
|
|
157
209
|
*/
|
|
158
|
-
function DraftRow({ search, warehouseId, currency, onPick, onFree, onAddBlank,
|
|
210
|
+
function DraftRow({ search, warehouseId, currency, onPick, onFree, onAddBlank, onCreate, entityLabel, span, }) {
|
|
159
211
|
const { t } = useTranslation();
|
|
160
212
|
const [text, setText] = useState('');
|
|
161
|
-
return (_jsxs("tr", { className: "border-t border-dashed", "data-slot": "line-draft-row", children: [_jsx("td", { className: CELL, children: _jsx(LineProductCell, { search: search, text: text, onTextChange: setText, onPick: (p, v) => {
|
|
213
|
+
return (_jsxs("tr", { className: "border-t border-dashed", "data-slot": "line-draft-row", children: [_jsx("td", { className: CELL, colSpan: span, children: _jsx(LineProductCell, { search: search, text: text, onTextChange: setText, onPick: (p, v) => {
|
|
162
214
|
setText('');
|
|
163
215
|
onPick(p, v);
|
|
164
216
|
}, onKeyDownClosed: (e) => {
|
|
@@ -171,7 +223,12 @@ function DraftRow({ search, warehouseId, currency, onPick, onFree, onAddBlank, t
|
|
|
171
223
|
onFree(typed);
|
|
172
224
|
else
|
|
173
225
|
onAddBlank();
|
|
174
|
-
}, warehouseId: warehouseId, currency: currency, ariaLabel: t('lineItems.searchProduct', { defaultValue: 'Buscar producto' }),
|
|
226
|
+
}, warehouseId: warehouseId, currency: currency, ariaLabel: t('lineItems.searchProduct', { defaultValue: 'Buscar producto' }), className: "border-dashed bg-transparent shadow-none", dataCell: "search", onCreate: onCreate
|
|
227
|
+
? (q) => {
|
|
228
|
+
setText('');
|
|
229
|
+
onCreate(q);
|
|
230
|
+
}
|
|
231
|
+
: undefined, entityLabel: entityLabel }) }), _jsx("td", { "aria-hidden": true }), _jsx("td", { "aria-hidden": true })] }));
|
|
175
232
|
}
|
|
176
233
|
/** Fila de totales alineada a la columna «Importe». */
|
|
177
234
|
function TotalRow({ span, label, value, slot, strong }) {
|
|
@@ -179,6 +236,8 @@ function TotalRow({ span, label, value, slot, strong }) {
|
|
|
179
236
|
}
|
|
180
237
|
/** Borrar renglón: aparece al pasar o enfocar la fila (siempre visible en móvil). */
|
|
181
238
|
const DELETE = 'size-8 text-muted-foreground opacity-0 transition-opacity hover:text-destructive focus-visible:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100 max-sm:opacity-100';
|
|
239
|
+
/** Lápiz «Editar producto» dentro de la celda: al pasar o enfocar la fila; siempre con teclado y en móvil. */
|
|
240
|
+
const EDIT = 'absolute right-0.5 top-1/2 size-7 -translate-y-1/2 text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 group-hover:opacity-100 group-focus-within:opacity-100 max-sm:opacity-100';
|
|
182
241
|
/** Celda densa: misma altura en todos los renglones. */
|
|
183
242
|
const CELL = 'px-2 py-1.5 align-top';
|
|
184
243
|
/** Inputs de celda: un poco más bajos que los del formulario (densidad de editor). */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProductVariant } from './product-search';
|
|
1
2
|
export type LineItemKind = 'item' | 'section' | 'note';
|
|
2
3
|
export interface LineItem {
|
|
3
4
|
/** Clave estable de UI (no se envía al backend). */
|
|
@@ -43,6 +44,33 @@ export interface LineItem {
|
|
|
43
44
|
* del host) — el SDK no conoce sus claves.
|
|
44
45
|
*/
|
|
45
46
|
extensions?: Record<string, string>;
|
|
47
|
+
/**
|
|
48
|
+
* Valores que el catálogo escribió en el renglón la última vez que se
|
|
49
|
+
* aplicó el producto (elegirlo, crearlo o editarlo desde la celda). Un campo
|
|
50
|
+
* cuyo valor actual difiere de su foto es un override del usuario («dirty»)
|
|
51
|
+
* y una edición del producto no lo pisa. No se serializa.
|
|
52
|
+
*/
|
|
53
|
+
catalog?: LineCatalogSnapshot;
|
|
54
|
+
/**
|
|
55
|
+
* Valores nuevos del catálogo que NO se aplicaron porque el usuario ya
|
|
56
|
+
* había cambiado ese campo a mano: la celda ofrece «aplicar». No se serializa.
|
|
57
|
+
*/
|
|
58
|
+
catalog_pending?: Partial<Pick<LineItem, CatalogLineField>>;
|
|
59
|
+
}
|
|
60
|
+
/** Campos del renglón que salen del producto y el usuario puede sobrescribir. */
|
|
61
|
+
export type CatalogLineField = 'description' | 'sku' | 'unit_price' | 'tax_rate' | 'unit';
|
|
62
|
+
export declare const CATALOG_LINE_FIELDS: readonly CatalogLineField[];
|
|
63
|
+
/** Foto de lo que el catálogo escribió en el renglón (por campo) y de qué producto/variante. */
|
|
64
|
+
export interface LineCatalogSnapshot {
|
|
65
|
+
description?: string;
|
|
66
|
+
sku?: string;
|
|
67
|
+
unit_price?: number;
|
|
68
|
+
tax_rate?: number;
|
|
69
|
+
unit?: string;
|
|
70
|
+
/** Id del producto del catálogo (el registro que se edita), aunque el renglón lleve una variante. */
|
|
71
|
+
product_ref?: string;
|
|
72
|
+
/** Variante elegida: al refrescar se vuelve a aplicar la misma. */
|
|
73
|
+
variant?: ProductVariant;
|
|
46
74
|
}
|
|
47
75
|
/** Qué hacer si `quantity > available`. */
|
|
48
76
|
export type StockPolicy = 'allow' | 'warn' | 'block';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"line-items.d.ts","sourceRoot":"","sources":["../../src/business/line-items.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"line-items.d.ts","sourceRoot":"","sources":["../../src/business/line-items.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// guardado de renglones fallaba (PIT-018) por mandar al API cadenas vacías y
|
|
4
4
|
// claves de UI; `serializeLineItems` es la única puerta de salida hacia el backend.
|
|
5
5
|
import { roundMoney, toAmount } from './format';
|
|
6
|
+
export const CATALOG_LINE_FIELDS = ['description', 'sku', 'unit_price', 'tax_rate', 'unit'];
|
|
6
7
|
let seq = 0;
|
|
7
8
|
/** Clave de UI única en el proceso. */
|
|
8
9
|
export function newLineKey() {
|
|
@@ -25,6 +25,12 @@ export interface LineProductCellProps {
|
|
|
25
25
|
className?: string;
|
|
26
26
|
/** Atributos de datos para que el grid ubique la celda (foco por teclado). */
|
|
27
27
|
dataCell?: string;
|
|
28
|
+
/** Alta del producto desde la celda, prellenada con lo buscado. */
|
|
29
|
+
onCreate?: (query: string) => void;
|
|
30
|
+
/** Nombre del modelo para «Crear …» (default «producto»). */
|
|
31
|
+
entityLabel?: string;
|
|
32
|
+
/** Detalle de qué se puede buscar: tooltip del buscador y estado sin coincidencias. */
|
|
33
|
+
hint?: string;
|
|
28
34
|
}
|
|
29
35
|
export declare const LineProductCell: import("react").ForwardRefExoticComponent<LineProductCellProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
30
36
|
//# sourceMappingURL=line-product-cell.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"line-product-cell.d.ts","sourceRoot":"","sources":["../../src/business/line-product-cell.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"line-product-cell.d.ts","sourceRoot":"","sources":["../../src/business/line-product-cell.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAGhE,OAAO,EAKH,KAAK,UAAU,EACf,KAAK,eAAe,EACvB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAErE,MAAM,MAAM,iBAAiB,GAAG,eAAe,CAAA;AAE/C,+CAA+C;AAC/C,MAAM,MAAM,cAAc,GAAG,UAAU,CAAA;AAEvC,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,iBAAiB,CAAA;IACzB,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,MAAM,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,cAAc,KAAK,IAAI,CAAA;IAClE;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAA;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,eAAe,mHA2D1B,CAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
// LineProductCell — buscador de producto DENTRO de la celda «Descripción» de un
|
|
3
3
|
// renglón (patrón de editor de facturas pro: Odoo/Holded/Linear). Es una
|
|
4
4
|
// configuración del <RecordPicker> compartido (trigger «input», texto libre,
|
|
@@ -9,15 +9,21 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
9
9
|
// Teclado: ↑/↓ recorren, Enter elige (el grid pasa el foco a «Cant.»), Esc
|
|
10
10
|
// cierra, Tab cierra y avanza normal. El texto escrito sin elegir queda como
|
|
11
11
|
// descripción libre (el renglón libre sigue funcionando igual).
|
|
12
|
+
//
|
|
13
|
+
// Con `onCreate`, la celda ofrece crear el producto como las apps top: «+» unido
|
|
14
|
+
// al buscador y «Crear producto «texto»» al pie de la lista, prellenado con lo
|
|
15
|
+
// buscado. El grid llena el renglón con el producto nuevo al guardarlo.
|
|
12
16
|
import { forwardRef, useState } from 'react';
|
|
13
17
|
import { useTranslation } from 'react-i18next';
|
|
14
18
|
import { RecordPicker } from '../record-picker';
|
|
15
19
|
import { ProductHitRow, productHitKey, productHitLabel, useProductSearch, } from './product-options';
|
|
16
|
-
export const LineProductCell = forwardRef(function LineProductCell({ search, text, onTextChange, onPick, onKeyDownClosed, warehouseId, currency, placeholder, ariaLabel, disabled, invalid, className, dataCell }, ref) {
|
|
20
|
+
export const LineProductCell = forwardRef(function LineProductCell({ search, text, onTextChange, onPick, onKeyDownClosed, warehouseId, currency, placeholder, ariaLabel, disabled, invalid, className, dataCell, onCreate, entityLabel, hint }, ref) {
|
|
17
21
|
const { t } = useTranslation();
|
|
18
22
|
// Solo se busca/abre mientras se escribe: enfocar un renglón libre ya
|
|
19
23
|
// capturado no despliega «sin resultados» encima de la tabla.
|
|
20
24
|
const [typing, setTyping] = useState(false);
|
|
21
25
|
const { hits, loading, minChars } = useProductSearch(typing ? text : '', search, typing);
|
|
22
|
-
|
|
26
|
+
const entity = entityLabel ?? t('lineItems.productEntity', { defaultValue: 'producto' });
|
|
27
|
+
const searchHint = hint ?? t('lineItems.searchHint', { defaultValue: 'Busca por nombre, SKU o código de barras' });
|
|
28
|
+
return (_jsx(RecordPicker, { trigger: "input", variant: "cell", freeText: true, anchorSlot: "line-product-cell", contentSlot: "line-product-results", minListWidth: "26.25rem", maxListWidth: "35rem", items: hits, loading: loading, minChars: minChars, query: text, onQueryChange: onTextChange, open: typing, onOpenChange: setTyping, getKey: productHitKey, getLabel: productHitLabel, renderItem: (hit) => _jsx(ProductHitRow, { hit: hit, warehouseId: warehouseId, currency: currency, compact: true }), onSelect: (hit) => onPick(hit.product, hit.variant), onUnhandledKeyDown: onKeyDownClosed, inputRef: ref, ariaLabel: ariaLabel, listLabel: ariaLabel, disabled: disabled, invalid: invalid, triggerClassName: className, triggerProps: { 'data-cell': dataCell, title: searchHint }, placeholder: placeholder ?? t('lineItems.searchPlaceholderShort', { defaultValue: 'Buscar producto…' }), loadingText: t('common.searching', { defaultValue: 'Buscando…' }), emptyText: _jsxs("span", { className: "block space-y-1", children: [_jsx("span", { className: "block", children: t('lineItems.noProduct', { defaultValue: 'Sin coincidencias. Se usará como descripción libre.' }) }), _jsx("span", { className: "block text-xs", children: searchHint })] }), onCreate: onCreate, entityLabel: entity, createLabel: t('lineItems.createProduct', { defaultValue: 'Crear {{entity}}', entity }), createFooterLabel: (q) => (_jsxs(_Fragment, { children: [t('lineItems.createProduct', { defaultValue: 'Crear {{entity}}', entity }), q ? _jsxs("span", { className: "text-muted-foreground", children: [" \u00AB", q, "\u00BB"] }) : null] })) }));
|
|
23
29
|
});
|
|
@@ -54,5 +54,5 @@ export function ProductHitTrailing({ hit, warehouseId, currency, compact = false
|
|
|
54
54
|
/** Option body: bold name (· variant), muted meta line, stock/price right. */
|
|
55
55
|
export function ProductHitRow({ hit, warehouseId, currency, compact }) {
|
|
56
56
|
const meta = productHitMeta(hit);
|
|
57
|
-
return (_jsxs("span", { className: "flex w-full min-w-0 items-center justify-between gap-3", children: [_jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "
|
|
57
|
+
return (_jsxs("span", { className: "flex w-full min-w-0 items-center justify-between gap-3", children: [_jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "line-clamp-2 break-words font-medium", title: productHitLabel(hit), children: productHitLabel(hit) }), meta && _jsx("span", { className: "block truncate text-xs text-muted-foreground", children: meta })] }), _jsx("span", { className: "flex shrink-0 items-center gap-2", children: _jsx(ProductHitTrailing, { hit: hit, warehouseId: warehouseId, currency: currency, compact: compact }) })] }));
|
|
58
58
|
}
|
|
@@ -770,7 +770,7 @@ function FieldRow({ field, record, value, mode, onChange, error, locked }) {
|
|
|
770
770
|
// CREATE for a caller-specified field (see `lockedFields`).
|
|
771
771
|
const isEditReadonly = (mode === 'edit' && !!field.readonly) || !!locked;
|
|
772
772
|
const { t } = useTranslation();
|
|
773
|
-
return (_jsxs("div", { className: "flex flex-col gap-1.5", "data-aby-field": field.key, children: [_jsxs(Label, { className: "text-
|
|
773
|
+
return (_jsxs("div", { className: "flex flex-col gap-1.5", "data-aby-field": field.key, children: [_jsxs(Label, { className: "text-sm font-medium text-foreground/80", children: [localizeFieldLabel(field.label, t), field.required && mode !== 'view' && !isEditReadonly && (_jsx("span", { className: "text-destructive ml-0.5", children: "*" }))] }), mode === 'view' ? (_jsx(ViewValue, { field: field, value: value, record: record })) : isEditReadonly ? (_jsx(ReadonlyEditField, { field: field, value: value })) : (_jsx(EditField, { field: field, value: value, onChange: onChange, record: record, invalid: !!error })), error && mode !== 'view' && (_jsx("p", { className: "text-destructive text-xs mt-1", children: error }))] }));
|
|
774
774
|
}
|
|
775
775
|
// ReadonlyEditField — the edit-mode rendering of a `readonly` (system-generated)
|
|
776
776
|
// field: a disabled, muted input that shows the current value without allowing
|
package/dist/field-grid.d.ts
CHANGED
|
@@ -13,9 +13,10 @@ export declare function FieldLabel({ htmlFor, required, tone, children, }: {
|
|
|
13
13
|
htmlFor?: string;
|
|
14
14
|
required?: boolean;
|
|
15
15
|
/**
|
|
16
|
-
* `
|
|
17
|
-
*
|
|
18
|
-
* uppercase labels
|
|
16
|
+
* `sentence` (default): sentence-case label — every form of the runtime
|
|
17
|
+
* (CRUD modal, action modal, DocumentEditor) reads the same and a column of
|
|
18
|
+
* uppercase labels no longer shouts. `caps`: small uppercase eyebrow, for
|
|
19
|
+
* callers that still want it.
|
|
19
20
|
*/
|
|
20
21
|
tone?: 'caps' | 'sentence';
|
|
21
22
|
children: ReactNode;
|
package/dist/field-grid.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-grid.d.ts","sourceRoot":"","sources":["../src/field-grid.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAM7F;AAED,wBAAgB,SAAS,CAAC,EACtB,QAAQ,EACR,SAAS,EACT,SAAS,GACZ,EAAE;IACC,QAAQ,EAAE,SAAS,CAAA;IACnB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB,+BAMA;AAED,wBAAgB,UAAU,CAAC,EACvB,OAAO,EACP,QAAQ,EACR,
|
|
1
|
+
{"version":3,"file":"field-grid.d.ts","sourceRoot":"","sources":["../src/field-grid.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAM7F;AAED,wBAAgB,SAAS,CAAC,EACtB,QAAQ,EACR,SAAS,EACT,SAAS,GACZ,EAAE;IACC,QAAQ,EAAE,SAAS,CAAA;IACnB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB,+BAMA;AAED,wBAAgB,UAAU,CAAC,EACvB,OAAO,EACP,QAAQ,EACR,IAAiB,EACjB,QAAQ,GACX,EAAE;IACC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;IAC1B,QAAQ,EAAE,SAAS,CAAA;CACtB,+BAcA"}
|
package/dist/field-grid.js
CHANGED
|
@@ -7,7 +7,7 @@ export function FieldGrid({ children, className }) {
|
|
|
7
7
|
export function FieldCell({ children, fullWidth, className, }) {
|
|
8
8
|
return (_jsx("div", { className: cn('flex min-w-0 flex-col gap-1.5', fullWidth && 'sm:col-span-2', className), children: children }));
|
|
9
9
|
}
|
|
10
|
-
export function FieldLabel({ htmlFor, required, tone = '
|
|
10
|
+
export function FieldLabel({ htmlFor, required, tone = 'sentence', children, }) {
|
|
11
11
|
return (_jsxs(Label, { htmlFor: htmlFor, className: tone === 'sentence'
|
|
12
12
|
? 'text-sm font-medium text-foreground/80'
|
|
13
13
|
: 'text-xs font-medium uppercase tracking-wide text-muted-foreground', children: [children, required && _jsx("span", { className: "ml-0.5 text-destructive", children: "*" })] }));
|
package/dist/record-picker.d.ts
CHANGED
|
@@ -134,6 +134,8 @@ export interface RecordPickerProps<T> {
|
|
|
134
134
|
inputRef?: Ref<HTMLInputElement>;
|
|
135
135
|
/** Floor for the list width (CSS length). Default 14rem. */
|
|
136
136
|
minListWidth?: string;
|
|
137
|
+
/** Ceiling for the list width (CSS length), e.g. under a very wide trigger. Default: none. */
|
|
138
|
+
maxListWidth?: string;
|
|
137
139
|
/** Rendered after the joined group (e.g. a scan button). */
|
|
138
140
|
after?: ReactNode;
|
|
139
141
|
/** Rendered under the control (badges, hints). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-picker.d.ts","sourceRoot":"","sources":["../src/record-picker.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"record-picker.d.ts","sourceRoot":"","sources":["../src/record-picker.tsx"],"names":[],"mappings":"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"}
|
package/dist/record-picker.js
CHANGED
|
@@ -111,7 +111,7 @@ function asKeys(value, multiple) {
|
|
|
111
111
|
return [String(value)];
|
|
112
112
|
}
|
|
113
113
|
export function RecordPicker(props) {
|
|
114
|
-
const { getKey, getLabel, items, search, debounceMs = 250, multiple = false, value, selected, onSelect, onClear, onRemove, renderItem, renderLead, getDescription, renderTrailing, renderValue, isItemDisabled, placeholder = 'Seleccionar…', searchPlaceholder = 'Buscar…', emptyText = 'Sin resultados', loadingText = 'Buscando…', errorText = 'No se pudo buscar. Inténtalo de nuevo.', minCharsText, clearLabel = 'Quitar selección', listLabel, entityLabel = '', onCreate, onEdit, createLabel, editLabel, createFooter = 'always', createFooterLabel, hideJoinedAction = false, trigger = 'button', variant = 'field', freeText = false, icon: Icon, id, ariaLabel, disabled = false, readOnly = false, invalid = false, slot, anchorSlot, contentSlot = 'record-picker-content', className, triggerClassName, triggerProps, inputRef, minListWidth = '14rem', after, below, beforeList, onInputKeyDown, onUnhandledKeyDown, } = props;
|
|
114
|
+
const { getKey, getLabel, items, search, debounceMs = 250, multiple = false, value, selected, onSelect, onClear, onRemove, renderItem, renderLead, getDescription, renderTrailing, renderValue, isItemDisabled, placeholder = 'Seleccionar…', searchPlaceholder = 'Buscar…', emptyText = 'Sin resultados', loadingText = 'Buscando…', errorText = 'No se pudo buscar. Inténtalo de nuevo.', minCharsText, clearLabel = 'Quitar selección', listLabel, entityLabel = '', onCreate, onEdit, createLabel, editLabel, createFooter = 'always', createFooterLabel, hideJoinedAction = false, trigger = 'button', variant = 'field', freeText = false, icon: Icon, id, ariaLabel, disabled = false, readOnly = false, invalid = false, slot, anchorSlot, contentSlot = 'record-picker-content', className, triggerClassName, triggerProps, inputRef, minListWidth = '14rem', maxListWidth, after, below, beforeList, onInputKeyDown, onUnhandledKeyDown, } = props;
|
|
115
115
|
const isInput = trigger === 'input';
|
|
116
116
|
const minChars = props.minChars ?? (isInput ? 2 : 0);
|
|
117
117
|
const showCheck = props.showCheck ?? !isInput;
|
|
@@ -278,7 +278,12 @@ export function RecordPicker(props) {
|
|
|
278
278
|
return (_jsx("div", { className: "px-2 py-6 text-center text-sm text-muted-foreground", "data-slot": "picker-empty", children: emptyText }));
|
|
279
279
|
})();
|
|
280
280
|
const optionClass = (on, dis) => cx('relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden', on && 'bg-accent text-accent-foreground', dis && 'pointer-events-none opacity-50');
|
|
281
|
-
const list = (_jsxs("div", { className: "
|
|
281
|
+
const list = (_jsxs("div", { className: "overflow-y-auto overflow-x-hidden overscroll-contain", "data-slot": "record-picker-list",
|
|
282
|
+
// Never taller than the room Radix measured on the side it opened
|
|
283
|
+
// (flipped above the field near the bottom of a modal it used to
|
|
284
|
+
// spill past the top of the viewport): 18rem at most, minus the
|
|
285
|
+
// search box in button mode; the list scrolls inside.
|
|
286
|
+
style: { maxHeight: `min(18rem, calc(var(--radix-popover-content-available-height, 18rem) - ${isInput ? '2px' : '2.75rem'}))` }, children: [beforeList, status, _jsxs("ul", { id: listId, role: "listbox", "aria-label": listLabel ?? ariaLabel ?? entityLabel ?? undefined, "aria-multiselectable": multiple || undefined, className: cx('p-1', count === 0 && 'hidden'), children: [rows.map((item, i) => {
|
|
282
287
|
const sel = isSelected(item);
|
|
283
288
|
const dis = !!isItemDisabled?.(item);
|
|
284
289
|
const on = i === active;
|
|
@@ -286,10 +291,12 @@ export function RecordPicker(props) {
|
|
|
286
291
|
return (_jsx("li", { id: `${listId}-o${i}`, role: "option", "aria-selected": multiple ? sel : on, "aria-disabled": dis || undefined, "data-active": on || undefined, "data-selected": sel || undefined, "data-slot": "record-picker-option", className: optionClass(on, dis), onMouseDown: (e) => e.preventDefault(), onMouseMove: () => {
|
|
287
292
|
if (!on && !dis)
|
|
288
293
|
setActive(i);
|
|
289
|
-
}, onClick: () => choose(i), children: renderItem ? (renderItem(item, { active: on, selected: sel, disabled: dis })) : (_jsxs(_Fragment, { children: [showCheck ? (_jsx(Check, { className: cx('size-4 shrink-0', sel ? 'opacity-100' : 'opacity-0'), "aria-hidden": true })) : null, renderLead?.(item, 'option'), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "
|
|
294
|
+
}, onClick: () => choose(i), children: renderItem ? (renderItem(item, { active: on, selected: sel, disabled: dis })) : (_jsxs(_Fragment, { children: [showCheck ? (_jsx(Check, { className: cx('size-4 shrink-0', sel ? 'opacity-100' : 'opacity-0'), "aria-hidden": true })) : null, renderLead?.(item, 'option'), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "line-clamp-2 break-words", children: getLabel(item) }), desc != null && desc !== '' ? (_jsx("span", { className: "truncate text-xs text-muted-foreground", children: desc })) : null] }), renderTrailing ? (_jsx("span", { className: "ml-auto flex shrink-0 items-center gap-2", children: renderTrailing(item, 'option') })) : null] })) }, getKey(item)));
|
|
290
295
|
}), showFooter ? (_jsxs(_Fragment, { children: [rows.length > 0 ? _jsx("li", { role: "presentation", className: "-mx-1 my-1 h-px bg-border" }) : null, _jsxs("li", { id: `${listId}-o${rows.length}`, role: "option", "aria-selected": active === rows.length, "data-active": active === rows.length || undefined, "data-slot": "picker-create", className: cx(optionClass(active === rows.length, false), 'text-primary'), onMouseDown: (e) => e.preventDefault(), onMouseMove: () => setActive(rows.length), onClick: runCreate, children: [_jsx(Plus, { className: "size-4 shrink-0", "aria-hidden": true }), _jsxs("span", { className: "truncate", children: [createFooterLabel ? createFooterLabel(trimmed) : `Crear ${entityLabel}`.trim(), !createFooterLabel && trimmed ? _jsxs("span", { className: "text-muted-foreground", children: [" \u00AB", trimmed, "\u00BB"] }) : null] })] })] })) : null] })] }));
|
|
291
296
|
const contentStyle = {
|
|
292
|
-
width:
|
|
297
|
+
width: maxListWidth
|
|
298
|
+
? `min(max(var(--radix-popover-trigger-width), ${minListWidth}), ${maxListWidth})`
|
|
299
|
+
: `max(var(--radix-popover-trigger-width), ${minListWidth})`,
|
|
293
300
|
maxWidth: 'calc(100vw - 1rem)',
|
|
294
301
|
zIndex: RECORD_PICKER_Z_INDEX,
|
|
295
302
|
};
|
package/package.json
CHANGED
|
@@ -47,10 +47,17 @@ describe('DocumentLinesGrid — select de producto en la celda', () => {
|
|
|
47
47
|
const input = screen.getByRole('combobox', { name: 'Buscar producto' })
|
|
48
48
|
const td = input.closest('td')!
|
|
49
49
|
expect(td).toBeTruthy()
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
// No es una barra a todo lo ancho: el renglón vacío conserva las celdas
|
|
51
|
+
// de «Importe» y acciones; el buscador usa el ancho de las columnas que
|
|
52
|
+
// ese renglón aún no llena (cantidad, precio…), así no se corta.
|
|
53
|
+
const row = td.closest('tr')!
|
|
54
|
+
expect(row.getAttribute('data-slot')).toBe('line-draft-row')
|
|
55
|
+
const columns = document.querySelectorAll('[data-slot="line-items-editor"] thead th').length
|
|
56
|
+
expect(td.colSpan).toBe(columns - 2)
|
|
57
|
+
expect(row.children.length).toBe(3)
|
|
58
|
+
// Placeholder corto que cabe; el detalle va como ayuda.
|
|
59
|
+
expect(input.getAttribute('placeholder')).toBe('Buscar producto…')
|
|
60
|
+
expect(input.getAttribute('title')).toMatch(/SKU o código de barras/)
|
|
54
61
|
})
|
|
55
62
|
|
|
56
63
|
it('la lista sale en un portal fuera del contenedor con overflow y Enter elige y pasa a «Cant.»', async () => {
|