Clientes -> arreglos varios: imports, formateo, quitar AG Grid
This commit is contained in:
parent
7380b425ea
commit
d062c4b5fe
@ -39,7 +39,6 @@
|
|||||||
"@repo/rdx-utils": "workspace:*",
|
"@repo/rdx-utils": "workspace:*",
|
||||||
"@repo/shadcn-ui": "workspace:*",
|
"@repo/shadcn-ui": "workspace:*",
|
||||||
"@tanstack/react-query": "^5.75.4",
|
"@tanstack/react-query": "^5.75.4",
|
||||||
"ag-grid-community": "^33.3.0",
|
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"http-status": "^2.1.0",
|
"http-status": "^2.1.0",
|
||||||
@ -48,7 +47,6 @@
|
|||||||
"react-hook-form": "^7.58.1",
|
"react-hook-form": "^7.58.1",
|
||||||
"react-i18next": "^15.5.1",
|
"react-i18next": "^15.5.1",
|
||||||
"react-router-dom": "^6.26.0",
|
"react-router-dom": "^6.26.0",
|
||||||
"sequelize": "^6.37.5",
|
|
||||||
"zod": "^4.1.11"
|
"zod": "^4.1.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
export * from "./json-tax-catalog.provider";
|
export * from "./json-tax-catalog.provider";
|
||||||
export * from "./spain-tax-catalog.provider";
|
export * from "./spain-tax-catalog.provider";
|
||||||
export * from "./tax-catalog-types";
|
|
||||||
export * from "./tax-catalog.provider";
|
export * from "./tax-catalog.provider";
|
||||||
|
export * from "./tax-catalog-types";
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
// --- Adaptador que carga el catálogo JSON en memoria e indexa por code ---
|
// --- Adaptador que carga el catálogo JSON en memoria e indexa por code ---
|
||||||
|
|
||||||
import { Maybe } from "@repo/rdx-utils";
|
import { Maybe } from "@repo/rdx-utils";
|
||||||
import { TaxCatalogType, TaxItemType, TaxLookupItems } from "./tax-catalog-types";
|
|
||||||
import { TaxCatalogProvider } from "./tax-catalog.provider";
|
import { TaxCatalogProvider } from "./tax-catalog.provider";
|
||||||
|
import { TaxCatalogType, TaxItemType, TaxLookupItems } from "./tax-catalog-types";
|
||||||
|
|
||||||
export class JsonTaxCatalogProvider implements TaxCatalogProvider {
|
export class JsonTaxCatalogProvider implements TaxCatalogProvider {
|
||||||
// Índice por código normalizado
|
// Índice por código normalizado
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { useDebounce } from '@repo/rdx-ui/components';
|
import { useDebounce } from "@repo/rdx-ui/components";
|
||||||
import {
|
import {
|
||||||
InputGroup,
|
InputGroup,
|
||||||
InputGroupAddon,
|
InputGroupAddon,
|
||||||
InputGroupButton,
|
InputGroupButton,
|
||||||
InputGroupInput
|
InputGroupInput,
|
||||||
} from "@repo/shadcn-ui/components";
|
} from "@repo/shadcn-ui/components";
|
||||||
import { Spinner } from "@repo/shadcn-ui/components/spinner";
|
import { Spinner } from "@repo/shadcn-ui/components/spinner";
|
||||||
import { SearchIcon, XIcon } from "lucide-react";
|
import { SearchIcon, XIcon } from "lucide-react";
|
||||||
@ -11,153 +11,140 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type SimpleSearchInputProps = {
|
type SimpleSearchInputProps = {
|
||||||
onSearchChange: (value: string) => void;
|
onSearchChange: (value: string) => void;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
maxHistory?: number;
|
maxHistory?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SEARCH_HISTORY_KEY = "search_history";
|
const SEARCH_HISTORY_KEY = "search_history";
|
||||||
|
|
||||||
export const SimpleSearchInput = ({
|
export const SimpleSearchInput = ({
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
loading = false,
|
loading = false,
|
||||||
maxHistory = 8,
|
maxHistory = 8,
|
||||||
}: SimpleSearchInputProps) => {
|
}: SimpleSearchInputProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [searchValue, setSearchValue] = useState("");
|
const [searchValue, setSearchValue] = useState("");
|
||||||
const [lastSearch, setLastSearch] = useState("");
|
const [lastSearch, setLastSearch] = useState("");
|
||||||
const [history, setHistory] = useState<string[]>([]);
|
const [history, setHistory] = useState<string[]>([]);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const debouncedValue = useDebounce(searchValue, 300);
|
const debouncedValue = useDebounce(searchValue, 300);
|
||||||
|
|
||||||
// Load from localStorage on mount
|
// Load from localStorage on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const stored = localStorage.getItem(SEARCH_HISTORY_KEY);
|
const stored = localStorage.getItem(SEARCH_HISTORY_KEY);
|
||||||
if (stored) setHistory(JSON.parse(stored));
|
if (stored) setHistory(JSON.parse(stored));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Emit changes after debounce
|
// Emit changes after debounce
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onSearchChange(debouncedValue);
|
onSearchChange(debouncedValue);
|
||||||
}, [debouncedValue, onSearchChange]);
|
}, [debouncedValue, onSearchChange]);
|
||||||
|
|
||||||
// Save history to localStorage
|
// Save history to localStorage
|
||||||
const saveHistory = (term: string) => {
|
const saveHistory = (term: string) => {
|
||||||
if (!term.trim()) return;
|
if (!term.trim()) return;
|
||||||
const cleaned = term.trim();
|
const cleaned = term.trim();
|
||||||
const newHistory = [cleaned, ...history.filter((h) => h !== cleaned)].slice(
|
const newHistory = [cleaned, ...history.filter((h) => h !== cleaned)].slice(0, maxHistory);
|
||||||
0,
|
setHistory(newHistory);
|
||||||
maxHistory
|
localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(newHistory));
|
||||||
);
|
};
|
||||||
setHistory(newHistory);
|
|
||||||
localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(newHistory));
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearHistory = () => {
|
const clearHistory = () => {
|
||||||
setHistory([]);
|
setHistory([]);
|
||||||
localStorage.removeItem(SEARCH_HISTORY_KEY);
|
localStorage.removeItem(SEARCH_HISTORY_KEY);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Input handlers
|
// Input handlers
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const cleaned = e.target.value.trimStart().replace(/\s+/g, " ");
|
const cleaned = e.target.value.trimStart().replace(/\s+/g, " ");
|
||||||
setSearchValue(cleaned);
|
setSearchValue(cleaned);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClear = () => {
|
const handleClear = () => {
|
||||||
setSearchValue("");
|
setSearchValue("");
|
||||||
onSearchChange("");
|
onSearchChange("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
// Enter → búsqueda inmediata
|
// Enter → búsqueda inmediata
|
||||||
if (e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
|
if (e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onSearchChange(searchValue);
|
onSearchChange(searchValue);
|
||||||
setLastSearch(searchValue);
|
setLastSearch(searchValue);
|
||||||
saveHistory(searchValue);
|
saveHistory(searchValue);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shift+Enter → repetir última búsqueda
|
// Shift+Enter → repetir última búsqueda
|
||||||
if (e.key === "Enter" && e.shiftKey) {
|
if (e.key === "Enter" && e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (lastSearch) {
|
if (lastSearch) {
|
||||||
onSearchChange(lastSearch);
|
onSearchChange(lastSearch);
|
||||||
setSearchValue(lastSearch);
|
setSearchValue(lastSearch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl/Cmd+Enter → limpiar
|
// Ctrl/Cmd+Enter → limpiar
|
||||||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleClear();
|
handleClear();
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectHistory = (term: string) => {
|
const handleSelectHistory = (term: string) => {
|
||||||
setSearchValue(term);
|
setSearchValue(term);
|
||||||
onSearchChange(term);
|
onSearchChange(term);
|
||||||
setLastSearch(term);
|
setLastSearch(term);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className='relative flex-1 max-w-xl'>
|
||||||
className="relative flex-1 max-w-xl"
|
<InputGroup className='bg-background' data-disabled={loading}>
|
||||||
aria-label={t("pages.list.searchPlaceholder", "Search input")}
|
<InputGroupInput
|
||||||
>
|
ref={inputRef}
|
||||||
|
placeholder={t("common.search_placeholder", "Search...")}
|
||||||
|
value={searchValue}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
inputMode='search'
|
||||||
|
autoComplete='off'
|
||||||
|
spellCheck={false}
|
||||||
|
disabled={loading}
|
||||||
|
onFocus={() => history.length > 0 && setOpen(true)}
|
||||||
|
/>
|
||||||
|
<InputGroupAddon>
|
||||||
|
<SearchIcon aria-hidden />
|
||||||
|
</InputGroupAddon>
|
||||||
|
|
||||||
<InputGroup className="bg-background" data-disabled={loading}>
|
<InputGroupAddon align='inline-end'>
|
||||||
<InputGroupInput
|
{loading && <Spinner aria-label={t("common.loading", "Loading")} />}
|
||||||
ref={inputRef}
|
{!searchValue && !loading && (
|
||||||
placeholder={t("common.search_placeholder", "Search...")}
|
<InputGroupButton
|
||||||
value={searchValue}
|
variant='secondary'
|
||||||
onChange={handleInputChange}
|
className='cursor-pointer'
|
||||||
onKeyDown={handleKeyDown}
|
onClick={() => onSearchChange(searchValue)}
|
||||||
inputMode="search"
|
>
|
||||||
autoComplete="off"
|
{t("common.search", "Search")}
|
||||||
spellCheck={false}
|
</InputGroupButton>
|
||||||
disabled={loading}
|
)}
|
||||||
onFocus={() => history.length > 0 && setOpen(true)}
|
{searchValue && !loading && (
|
||||||
/>
|
<InputGroupButton
|
||||||
<InputGroupAddon>
|
variant='secondary'
|
||||||
<SearchIcon aria-hidden />
|
className='cursor-pointer'
|
||||||
</InputGroupAddon>
|
aria-label={t("common.clear", "Clear search")}
|
||||||
|
onClick={handleClear}
|
||||||
<InputGroupAddon align="inline-end">
|
>
|
||||||
{loading && (
|
<XIcon className='size-4' aria-hidden />
|
||||||
<Spinner aria-label={t("common.loading", "Loading")} />
|
<span className='sr-only'>{t("common.clear", "Clear")}</span>
|
||||||
)}
|
</InputGroupButton>
|
||||||
{!searchValue && !loading && (
|
)}
|
||||||
<InputGroupButton
|
</InputGroupAddon>
|
||||||
variant="secondary"
|
</InputGroup>
|
||||||
className="cursor-pointer"
|
</div>
|
||||||
onClick={() => onSearchChange(searchValue)}
|
);
|
||||||
>
|
|
||||||
{t("common.search", "Search")}
|
|
||||||
</InputGroupButton>
|
|
||||||
)}
|
|
||||||
{searchValue && !loading && (
|
|
||||||
<InputGroupButton
|
|
||||||
variant="secondary"
|
|
||||||
className="cursor-pointer"
|
|
||||||
aria-label={t("common.clear", "Clear search")}
|
|
||||||
onClick={handleClear}
|
|
||||||
>
|
|
||||||
<XIcon className="size-4" aria-hidden />
|
|
||||||
<span className="sr-only">{t("common.clear", "Clear")}</span>
|
|
||||||
</InputGroupButton>
|
|
||||||
)}
|
|
||||||
</InputGroupAddon>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,2 @@
|
|||||||
import { AllCommunityModule, ModuleRegistry } from "ag-grid-community";
|
|
||||||
|
|
||||||
ModuleRegistry.registerModules([AllCommunityModule]);
|
|
||||||
|
|
||||||
export * from "./form";
|
export * from "./form";
|
||||||
export * from "./page-header";
|
export * from "./page-header";
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import { Button } from '@repo/shadcn-ui/components';
|
import { Button } from "@repo/shadcn-ui/components";
|
||||||
import { cn } from '@repo/shadcn-ui/lib/utils';
|
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||||
import { ChevronLeftIcon } from 'lucide-react';
|
import { ChevronLeftIcon } from "lucide-react";
|
||||||
// features/common/components/page-header.tsx
|
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
|
||||||
interface PageHeaderProps {
|
interface PageHeaderProps {
|
||||||
backIcon?: ReactNode;
|
backIcon?: ReactNode;
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
@ -15,8 +13,13 @@ interface PageHeaderProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function PageHeader({
|
||||||
export function PageHeader({ backIcon, title, description, rightSlot, className }: PageHeaderProps) {
|
backIcon,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
rightSlot,
|
||||||
|
className,
|
||||||
|
}: PageHeaderProps) {
|
||||||
return (
|
return (
|
||||||
<div className={cn("pt-6 pb-6 lg:flex lg:items-center lg:justify-between", className)}>
|
<div className={cn("pt-6 pb-6 lg:flex lg:items-center lg:justify-between", className)}>
|
||||||
{/* Lado izquierdo */}
|
{/* Lado izquierdo */}
|
||||||
@ -34,16 +37,16 @@ export function PageHeader({ backIcon, title, description, rightSlot, className
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 className='h-8 text-xl font-semibold text-foreground sm:truncate sm:tracking-tight'>{title}</h2>
|
<h2 className='h-8 text-xl font-semibold text-foreground sm:truncate sm:tracking-tight'>
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
{description && <p className='text-sm text-muted-foreground'>{description}</p>}
|
{description && <p className='text-sm text-muted-foreground'>{description}</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Lado derecho parametrizable */}
|
{/* Lado derecho parametrizable */}
|
||||||
<div className="mt-4 flex lg:mt-0 lg:ml-4">
|
<div className='mt-4 flex lg:mt-0 lg:ml-4'>{rightSlot}</div>
|
||||||
{rightSlot}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ const MODULE_VERSION = "1.0.0";
|
|||||||
export const CoreModuleManifiest: IModuleClient = {
|
export const CoreModuleManifiest: IModuleClient = {
|
||||||
name: MODULE_NAME,
|
name: MODULE_NAME,
|
||||||
version: MODULE_VERSION,
|
version: MODULE_VERSION,
|
||||||
dependencies: ["core"],
|
dependencies: [],
|
||||||
protected: true,
|
protected: true,
|
||||||
layout: "app",
|
layout: "app",
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user