diff --git a/modules/core/src/web/components/form/index.ts b/modules/core/src/web/components/form/index.ts index 69a7258c..e7c01d71 100644 --- a/modules/core/src/web/components/form/index.ts +++ b/modules/core/src/web/components/form/index.ts @@ -1 +1,2 @@ export * from "./form-debug.tsx"; +export * from "./simple-search-input.tsx"; diff --git a/modules/core/src/web/components/form/simple-search-input.tsx b/modules/core/src/web/components/form/simple-search-input.tsx new file mode 100644 index 00000000..0a349f78 --- /dev/null +++ b/modules/core/src/web/components/form/simple-search-input.tsx @@ -0,0 +1,163 @@ +import { useDebounce } from '@repo/rdx-ui/components'; +import { + InputGroup, + InputGroupAddon, + InputGroupButton, + InputGroupInput +} from "@repo/shadcn-ui/components"; +import { Spinner } from "@repo/shadcn-ui/components/spinner"; +import { SearchIcon, XIcon } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; + +type SimpleSearchInputProps = { + onSearchChange: (value: string) => void; + loading?: boolean; + maxHistory?: number; +}; + +const SEARCH_HISTORY_KEY = "search_history"; + +export const SimpleSearchInput = ({ + onSearchChange, + loading = false, + maxHistory = 8, +}: SimpleSearchInputProps) => { + const { t } = useTranslation(); + const [searchValue, setSearchValue] = useState(""); + const [lastSearch, setLastSearch] = useState(""); + const [history, setHistory] = useState([]); + const [open, setOpen] = useState(false); + const inputRef = useRef(null); + + const debouncedValue = useDebounce(searchValue, 300); + + // Load from localStorage on mount + useEffect(() => { + const stored = localStorage.getItem(SEARCH_HISTORY_KEY); + if (stored) setHistory(JSON.parse(stored)); + }, []); + + // Emit changes after debounce + useEffect(() => { + onSearchChange(debouncedValue); + }, [debouncedValue, onSearchChange]); + + // Save history to localStorage + const saveHistory = (term: string) => { + if (!term.trim()) return; + const cleaned = term.trim(); + const newHistory = [cleaned, ...history.filter((h) => h !== cleaned)].slice( + 0, + maxHistory + ); + setHistory(newHistory); + localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(newHistory)); + }; + + const clearHistory = () => { + setHistory([]); + localStorage.removeItem(SEARCH_HISTORY_KEY); + }; + + // Input handlers + const handleInputChange = (e: React.ChangeEvent) => { + const cleaned = e.target.value.trimStart().replace(/\s+/g, " "); + setSearchValue(cleaned); + }; + + const handleClear = () => { + setSearchValue(""); + onSearchChange(""); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + // Enter → búsqueda inmediata + if (e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey) { + e.preventDefault(); + onSearchChange(searchValue); + setLastSearch(searchValue); + saveHistory(searchValue); + setOpen(false); + } + + // Shift+Enter → repetir última búsqueda + if (e.key === "Enter" && e.shiftKey) { + e.preventDefault(); + if (lastSearch) { + onSearchChange(lastSearch); + setSearchValue(lastSearch); + } + } + + // Ctrl/Cmd+Enter → limpiar + if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { + e.preventDefault(); + handleClear(); + inputRef.current?.focus(); + } + }; + + const handleSelectHistory = (term: string) => { + setSearchValue(term); + onSearchChange(term); + setLastSearch(term); + setOpen(false); + }; + + return ( +
+ + + history.length > 0 && setOpen(true)} + /> + + + + + + {loading && ( + + )} + {!searchValue && !loading && ( + onSearchChange(searchValue)} + > + {t("common.search", "Search")} + + )} + {searchValue && !loading && ( + + + {t("common.clear", "Clear")} + + )} + + + + + +
+ + ); +}; diff --git a/modules/customer-invoices/src/web/components/editor/invoice-totals.tsx b/modules/customer-invoices/src/web/components/editor/invoice-totals.tsx index 1cc51340..1cb17346 100644 --- a/modules/customer-invoices/src/web/components/editor/invoice-totals.tsx +++ b/modules/customer-invoices/src/web/components/editor/invoice-totals.tsx @@ -28,7 +28,7 @@ export const InvoiceTotals = (props: ComponentProps<"fieldset">) => { {t("form_groups.totals.description")} - +
{/* Sección: Subtotal y Descuentos */} @@ -128,9 +128,9 @@ export const InvoiceTotals = (props: ComponentProps<"fieldset">) => { -
- Total de la factura - +
+ Total de la factura + {formatCurrency(getValues('total_amount'), 2, currency_code, language_code)}
diff --git a/packages/rdx-ui/src/components/datatable/data-table.tsx b/packages/rdx-ui/src/components/datatable/data-table.tsx index 8e73ec76..9e40b4e8 100644 --- a/packages/rdx-ui/src/components/datatable/data-table.tsx +++ b/packages/rdx-ui/src/components/datatable/data-table.tsx @@ -228,7 +228,7 @@ export function DataTable({ role="button" tabIndex={0} data-state={row.getIsSelected() && "selected"} - className={`group bg-background ${readOnly ? "cursor-default" : "cursor-pointer"}`} + className={"group bg-background cursor-pointer"} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") onRowClick?.(row.original, rowIndex, e as any); }}