Uecko_ERP/modules/customer-invoices/src/web/hooks/use-items-table-navigation.ts

113 lines
3.0 KiB
TypeScript
Raw Normal View History

2025-10-07 16:38:03 +00:00
import * as React from "react";
import { FieldValues, UseFormReturn, useFieldArray } from "react-hook-form";
type UseItemsTableNavigationOptions = {
name: string; // Ruta del array, p.ej. "items"
createEmpty: () => Record<string, unknown>;
firstEditableField?: string; // Primer campo editable para enfocar al crear/ir a la fila siguiente (p.ej. "description")
};
export function useItemsTableNavigation(
form: UseFormReturn<FieldValues>,
{ name, createEmpty, firstEditableField = "description" }: UseItemsTableNavigationOptions
) {
const { control, getValues, setFocus } = form;
const fa = useFieldArray({ control, name });
const length = React.useCallback(() => {
const arr = getValues(name) as unknown[];
return Array.isArray(arr) ? arr.length : 0;
}, [getValues, name]);
const focusRowFirstField = React.useCallback(
(rowIndex: number) => {
queueMicrotask(() => {
setFocus(`${name}.${rowIndex}.${firstEditableField}` as any, { shouldSelect: true });
});
},
[name, firstEditableField, setFocus]
);
const addEmpty = React.useCallback(
(atEnd = true, index?: number, initial?: Record<string, unknown>) => {
const row = { ...createEmpty(), ...(initial ?? {}) };
if (!atEnd && typeof index === "number") fa.insert(index, row);
else fa.append(row);
},
[fa, createEmpty]
);
const duplicate = React.useCallback(
(i: number) => {
const curr = getValues(`${name}.${i}`) as Record<string, unknown> | undefined;
if (!curr) return;
const clone =
typeof structuredClone === "function"
? structuredClone(curr)
: JSON.parse(JSON.stringify(curr));
// RHF añade un id interno en fields; por si acaso: crear un objeto sin la propiedad id
const { id: _id, ...sanitized } = clone as Record<string, unknown>;
fa.insert(i + 1, sanitized);
},
[fa, getValues, name]
);
const remove = React.useCallback(
(i: number) => {
if (i < 0 || i >= length()) return;
fa.remove(i);
},
[fa, length]
);
const moveUp = React.useCallback(
(i: number) => {
if (i <= 0) return;
fa.move(i, i - 1);
},
[fa]
);
const moveDown = React.useCallback(
(i: number) => {
const len = length();
if (i < 0 || i >= len - 1) return;
fa.move(i, i + 1);
},
[fa, length]
);
const onTabFromLastCell = React.useCallback(
(rowIndex: number) => {
const len = length();
if (rowIndex === len - 1) {
addEmpty(true);
focusRowFirstField(len);
} else {
focusRowFirstField(rowIndex + 1);
}
},
[length, addEmpty, focusRowFirstField]
);
const onShiftTabFromFirstCell = React.useCallback(
(rowIndex: number) => {
if (rowIndex <= 0) return;
focusRowFirstField(rowIndex - 1);
},
[focusRowFirstField]
);
return {
fa, // { fields, append, remove, insert, move, ... }
addEmpty,
duplicate,
remove,
moveUp,
moveDown,
onTabFromLastCell,
onShiftTabFromFirstCell,
focusRowFirstField,
};
}