@axzydev/axzy_ui_system 1.2.1 → 1.2.3
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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +82 -1
- package/dist/index.css.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/App.tsx +354 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/components/alert/alert.props.ts +13 -0
- package/src/components/alert/alert.stories.tsx +41 -0
- package/src/components/alert/alert.tsx +53 -0
- package/src/components/avatar/avatar.props.ts +14 -0
- package/src/components/avatar/avatar.stories.tsx +46 -0
- package/src/components/avatar/avatar.tsx +53 -0
- package/src/components/badget/badget.props.ts +12 -0
- package/src/components/badget/badget.stories.tsx +76 -0
- package/src/components/badget/badget.tsx +61 -0
- package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
- package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
- package/src/components/button/button.props.ts +18 -0
- package/src/components/button/button.stories.tsx +174 -0
- package/src/components/button/button.tsx +117 -0
- package/src/components/calendar/calendar.props.ts +33 -0
- package/src/components/calendar/calendar.stories.tsx +91 -0
- package/src/components/calendar/calendar.tsx +608 -0
- package/src/components/calendar/index.ts +3 -0
- package/src/components/card/card.props.ts +13 -0
- package/src/components/card/card.stories.tsx +58 -0
- package/src/components/card/card.tsx +79 -0
- package/src/components/checkbox/checkbox.props.ts +11 -0
- package/src/components/checkbox/checkbox.stories.tsx +54 -0
- package/src/components/checkbox/checkbox.tsx +52 -0
- package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
- package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
- package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
- package/src/components/data-table/ITDataTable.stories.tsx +213 -0
- package/src/components/data-table/dataTable.props.ts +69 -0
- package/src/components/data-table/dataTable.tsx +313 -0
- package/src/components/date-picker/date-picker.props.ts +30 -0
- package/src/components/date-picker/date-picker.stories.tsx +90 -0
- package/src/components/date-picker/datePicker.tsx +307 -0
- package/src/components/dialog/dialog.props.ts +9 -0
- package/src/components/dialog/dialog.stories.tsx +80 -0
- package/src/components/dialog/dialog.tsx +88 -0
- package/src/components/divider/divider.props.ts +8 -0
- package/src/components/divider/divider.stories.tsx +34 -0
- package/src/components/divider/divider.tsx +21 -0
- package/src/components/drawer/drawer.props.ts +14 -0
- package/src/components/drawer/drawer.stories.tsx +41 -0
- package/src/components/drawer/drawer.tsx +53 -0
- package/src/components/dropfile/dropfile.stories.tsx +75 -0
- package/src/components/dropfile/dropfile.tsx +407 -0
- package/src/components/empty-state/empty-state.props.ts +9 -0
- package/src/components/empty-state/empty-state.stories.tsx +20 -0
- package/src/components/empty-state/empty-state.tsx +21 -0
- package/src/components/flex/flex.props.ts +22 -0
- package/src/components/flex/flex.stories.tsx +71 -0
- package/src/components/flex/flex.tsx +79 -0
- package/src/components/form-builder/fieldRenderer.tsx +218 -0
- package/src/components/form-builder/formBuilder.context.tsx +70 -0
- package/src/components/form-builder/formBuilder.props.ts +43 -0
- package/src/components/form-builder/formBuilder.stories.tsx +317 -0
- package/src/components/form-builder/formBuilder.tsx +186 -0
- package/src/components/form-builder/useFormBuilder.ts +80 -0
- package/src/components/form-header/form-header.props.ts +5 -0
- package/src/components/form-header/form-header.tsx +38 -0
- package/src/components/grid/grid.props.ts +17 -0
- package/src/components/grid/grid.stories.tsx +72 -0
- package/src/components/grid/grid.tsx +69 -0
- package/src/components/image/image.props.ts +7 -0
- package/src/components/image/image.tsx +38 -0
- package/src/components/input/input.props.ts +49 -0
- package/src/components/input/input.stories.tsx +115 -0
- package/src/components/input/input.tsx +615 -0
- package/src/components/layout/layout.props.ts +10 -0
- package/src/components/layout/layout.stories.tsx +114 -0
- package/src/components/layout/layout.tsx +80 -0
- package/src/components/loader/loader.props.ts +8 -0
- package/src/components/loader/loader.stories.tsx +105 -0
- package/src/components/loader/loader.tsx +108 -0
- package/src/components/navbar/navbar.props.ts +37 -0
- package/src/components/navbar/navbar.tsx +328 -0
- package/src/components/page/page.props.ts +19 -0
- package/src/components/page/page.stories.tsx +98 -0
- package/src/components/page/page.tsx +90 -0
- package/src/components/page-header/page-header.props.ts +11 -0
- package/src/components/page-header/page-header.stories.tsx +61 -0
- package/src/components/page-header/page-header.tsx +62 -0
- package/src/components/pagination/pagination.props.ts +53 -0
- package/src/components/pagination/pagination.stories.tsx +111 -0
- package/src/components/pagination/pagination.tsx +241 -0
- package/src/components/popover/popover.props.ts +12 -0
- package/src/components/popover/popover.stories.tsx +25 -0
- package/src/components/popover/popover.tsx +45 -0
- package/src/components/progress/progress.props.ts +12 -0
- package/src/components/progress/progress.stories.tsx +40 -0
- package/src/components/progress/progress.tsx +52 -0
- package/src/components/radio/radio.props.ts +16 -0
- package/src/components/radio/radio.stories.tsx +50 -0
- package/src/components/radio/radio.tsx +58 -0
- package/src/components/search-select/index.ts +2 -0
- package/src/components/search-select/search-select.props.ts +46 -0
- package/src/components/search-select/search-select.stories.tsx +129 -0
- package/src/components/search-select/search-select.tsx +229 -0
- package/src/components/searchTable/components/EditableCell.tsx +149 -0
- package/src/components/searchTable/components/PaginationControls.tsx +86 -0
- package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
- package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
- package/src/components/searchTable/components/SearchInput.tsx +33 -0
- package/src/components/searchTable/components/SortButton.tsx +50 -0
- package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
- package/src/components/searchTable/components/TableHeader.tsx +35 -0
- package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
- package/src/components/searchTable/components/TableRow.tsx +144 -0
- package/src/components/searchTable/searchTable.props.ts +56 -0
- package/src/components/searchTable/searchTable.tsx +187 -0
- package/src/components/segmented-control/segmented-control.props.ts +18 -0
- package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
- package/src/components/segmented-control/segmented-control.tsx +52 -0
- package/src/components/select/select.props.ts +25 -0
- package/src/components/select/select.stories.tsx +86 -0
- package/src/components/select/select.tsx +150 -0
- package/src/components/sidebar/sidebar.props.ts +28 -0
- package/src/components/sidebar/sidebar.stories.tsx +117 -0
- package/src/components/sidebar/sidebar.tsx +313 -0
- package/src/components/skeleton/skeleton.props.ts +12 -0
- package/src/components/skeleton/skeleton.stories.tsx +30 -0
- package/src/components/skeleton/skeleton.tsx +45 -0
- package/src/components/slide/slide.props.ts +45 -0
- package/src/components/slide/slide.stories.tsx +121 -0
- package/src/components/slide/slide.tsx +109 -0
- package/src/components/slider/slider.props.ts +10 -0
- package/src/components/slider/slider.stories.tsx +30 -0
- package/src/components/slider/slider.tsx +49 -0
- package/src/components/stack/stack.props.ts +19 -0
- package/src/components/stack/stack.stories.tsx +79 -0
- package/src/components/stack/stack.tsx +79 -0
- package/src/components/stat-card/stat-card.props.ts +13 -0
- package/src/components/stat-card/stat-card.stories.tsx +41 -0
- package/src/components/stat-card/stat-card.tsx +44 -0
- package/src/components/stepper/stepper.css +26 -0
- package/src/components/stepper/stepper.props.ts +29 -0
- package/src/components/stepper/stepper.stories.tsx +155 -0
- package/src/components/stepper/stepper.tsx +227 -0
- package/src/components/table/table.props.ts +43 -0
- package/src/components/table/table.stories.tsx +189 -0
- package/src/components/table/table.tsx +376 -0
- package/src/components/tabs/tabs.props.ts +18 -0
- package/src/components/tabs/tabs.stories.tsx +32 -0
- package/src/components/tabs/tabs.tsx +74 -0
- package/src/components/text/text.props.ts +9 -0
- package/src/components/text/text.tsx +20 -0
- package/src/components/textarea/textarea.props.ts +15 -0
- package/src/components/textarea/textarea.stories.tsx +27 -0
- package/src/components/textarea/textarea.tsx +55 -0
- package/src/components/theme-provider/themeProvider.props.ts +28 -0
- package/src/components/theme-provider/themeProvider.tsx +1854 -0
- package/src/components/time-picker/timePicker.props.ts +16 -0
- package/src/components/time-picker/timePicker.stories.tsx +131 -0
- package/src/components/time-picker/timePicker.tsx +317 -0
- package/src/components/toast/toast.css +32 -0
- package/src/components/toast/toast.props.ts +13 -0
- package/src/components/toast/toast.stories.tsx +138 -0
- package/src/components/toast/toast.tsx +87 -0
- package/src/components/tooltip/tooltip.props.ts +11 -0
- package/src/components/tooltip/tooltip.stories.tsx +20 -0
- package/src/components/tooltip/tooltip.tsx +55 -0
- package/src/components/topbar/topbar.props.ts +21 -0
- package/src/components/topbar/topbar.stories.tsx +80 -0
- package/src/components/topbar/topbar.tsx +205 -0
- package/src/components/triple-filter/tripleFilter.props.ts +15 -0
- package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
- package/src/components/triple-filter/tripleFilter.tsx +50 -0
- package/src/dev.css +2 -0
- package/src/hooks/useClickOutside.ts +21 -0
- package/src/hooks/useDebouncedSearch.ts +55 -0
- package/src/hooks/useEditableRow.ts +157 -0
- package/src/hooks/useTableState.ts +122 -0
- package/src/index.css +168 -0
- package/src/index.ts +165 -0
- package/src/main.tsx +9 -0
- package/src/showcases/DataShowcases.tsx +260 -0
- package/src/showcases/FeedbackShowcases.tsx +268 -0
- package/src/showcases/FormShowcases.tsx +1159 -0
- package/src/showcases/HomeShowcase.tsx +324 -0
- package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
- package/src/showcases/NavigationShowcases.tsx +193 -0
- package/src/showcases/PageShowcases.tsx +207 -0
- package/src/showcases/ShowcaseLayout.tsx +139 -0
- package/src/showcases/StructureShowcases.tsx +152 -0
- package/src/types/badget.types.ts +37 -0
- package/src/types/button.types.ts +16 -0
- package/src/types/colors.types.ts +3 -0
- package/src/types/field.types.ts +103 -0
- package/src/types/formik.types.ts +15 -0
- package/src/types/input.types.ts +14 -0
- package/src/types/loader.types.ts +9 -0
- package/src/types/sizes.types.ts +1 -0
- package/src/types/table.types.ts +15 -0
- package/src/types/toast.types.ts +8 -0
- package/src/types/yup.types.ts +11 -0
- package/src/utils/color.utils.ts +99 -0
- package/src/utils/styles.ts +120 -0
- package/src/utils/table.utils.ts +10 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ITText from "@/components/text/text";
|
|
3
|
+
|
|
4
|
+
interface PaginationInfoProps {
|
|
5
|
+
currentCount: number;
|
|
6
|
+
totalCount: number;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function PaginationInfo({
|
|
11
|
+
currentCount,
|
|
12
|
+
totalCount,
|
|
13
|
+
className = "",
|
|
14
|
+
}: PaginationInfoProps) {
|
|
15
|
+
return (
|
|
16
|
+
<ITText as="span" className={`text-sm text-gray-700 ${className}`}>
|
|
17
|
+
Mostrando {currentCount} de {totalCount} resultados
|
|
18
|
+
</ITText>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import SearchInput from "./SearchInput";
|
|
3
|
+
import SortButton from "./SortButton";
|
|
4
|
+
import ITButton from "@/components/button/button";
|
|
5
|
+
import ITText from "@/components/text/text";
|
|
6
|
+
|
|
7
|
+
interface SearchAndSortBarProps {
|
|
8
|
+
searchTerm: string;
|
|
9
|
+
onSearchChange: (value: string) => void;
|
|
10
|
+
onClearSearch: () => void;
|
|
11
|
+
onGlobalSort: () => void;
|
|
12
|
+
sortConfig?: {
|
|
13
|
+
key: string;
|
|
14
|
+
direction: "asc" | "desc";
|
|
15
|
+
};
|
|
16
|
+
searchInputPlaceholder?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function SearchAndSortBar({
|
|
20
|
+
searchTerm,
|
|
21
|
+
onSearchChange,
|
|
22
|
+
onClearSearch,
|
|
23
|
+
onGlobalSort,
|
|
24
|
+
sortConfig,
|
|
25
|
+
searchInputPlaceholder = "Buscar en todos los campos...",
|
|
26
|
+
}: SearchAndSortBarProps) {
|
|
27
|
+
return (
|
|
28
|
+
<div className="bg-gray-50 px-6 py-4 border-b border-gray-200">
|
|
29
|
+
<div className="flex items-center gap-4">
|
|
30
|
+
<SearchInput
|
|
31
|
+
placeholder={searchInputPlaceholder}
|
|
32
|
+
value={searchTerm}
|
|
33
|
+
onChange={onSearchChange}
|
|
34
|
+
/>
|
|
35
|
+
|
|
36
|
+
<SortButton sortConfig={sortConfig} onClick={onGlobalSort} />
|
|
37
|
+
|
|
38
|
+
{searchTerm && (
|
|
39
|
+
<ITButton
|
|
40
|
+
size="small"
|
|
41
|
+
variant="outlined"
|
|
42
|
+
color="danger"
|
|
43
|
+
onClick={onClearSearch}
|
|
44
|
+
ariaLabel="Limpiar búsqueda"
|
|
45
|
+
title="Limpiar búsqueda"
|
|
46
|
+
>
|
|
47
|
+
<ITText as="span">Limpiar</ITText>
|
|
48
|
+
</ITButton>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import ITInput from "@/components/input/input";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { FaSearch } from "react-icons/fa";
|
|
4
|
+
|
|
5
|
+
interface SearchInputProps {
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
value: string;
|
|
8
|
+
onChange: (value: string) => void;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function SearchInput({
|
|
13
|
+
placeholder = "Buscar en todos los campos...",
|
|
14
|
+
value,
|
|
15
|
+
onChange,
|
|
16
|
+
className = "",
|
|
17
|
+
}: SearchInputProps) {
|
|
18
|
+
return (
|
|
19
|
+
<div className={`relative flex-1 ${className}`}>
|
|
20
|
+
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
21
|
+
<FaSearch className="h-5 w-5 text-gray-400" />
|
|
22
|
+
</div>
|
|
23
|
+
<ITInput
|
|
24
|
+
name="global-search"
|
|
25
|
+
placeholder={placeholder}
|
|
26
|
+
value={value}
|
|
27
|
+
onChange={(e) => onChange(e.target.value)}
|
|
28
|
+
onBlur={() => {}}
|
|
29
|
+
className="pl-10 w-full"
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
|
3
|
+
import ITText from "@/components/text/text";
|
|
4
|
+
|
|
5
|
+
interface SortButtonProps {
|
|
6
|
+
sortConfig?: {
|
|
7
|
+
key: string;
|
|
8
|
+
direction: "asc" | "desc";
|
|
9
|
+
};
|
|
10
|
+
onClick: () => void;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function SortButton({
|
|
15
|
+
sortConfig,
|
|
16
|
+
onClick,
|
|
17
|
+
className = "",
|
|
18
|
+
}: SortButtonProps) {
|
|
19
|
+
return (
|
|
20
|
+
<button
|
|
21
|
+
onClick={onClick}
|
|
22
|
+
className={`p-3 rounded-lg bg-white border border-gray-300 hover:bg-gray-50 transition-colors duration-200 flex items-center gap-2 min-w-[120px] ${className}`}
|
|
23
|
+
aria-label={`Ordenar tabla ${
|
|
24
|
+
sortConfig
|
|
25
|
+
? sortConfig.direction === "asc"
|
|
26
|
+
? "descendente"
|
|
27
|
+
: "ascendente"
|
|
28
|
+
: "ascendente"
|
|
29
|
+
}`}
|
|
30
|
+
title="Ordenar tabla"
|
|
31
|
+
>
|
|
32
|
+
{sortConfig ? (
|
|
33
|
+
sortConfig.direction === "asc" ? (
|
|
34
|
+
<FaSortUp className="w-4 h-4 text-slate-500" />
|
|
35
|
+
) : (
|
|
36
|
+
<FaSortDown className="w-4 h-4 text-slate-500" />
|
|
37
|
+
)
|
|
38
|
+
) : (
|
|
39
|
+
<FaSort className="w-4 h-4 text-gray-500" />
|
|
40
|
+
)}
|
|
41
|
+
<ITText as="span" className="text-sm font-medium text-gray-700">
|
|
42
|
+
{sortConfig
|
|
43
|
+
? sortConfig.direction === "asc"
|
|
44
|
+
? "Asc ↑"
|
|
45
|
+
: "Desc ↓"
|
|
46
|
+
: "Ordenar"}
|
|
47
|
+
</ITText>
|
|
48
|
+
</button>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ITText from "@/components/text/text";
|
|
3
|
+
|
|
4
|
+
interface TableEmptyStateProps {
|
|
5
|
+
colSpan: number;
|
|
6
|
+
searchTerm: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function TableEmptyState({
|
|
10
|
+
colSpan,
|
|
11
|
+
searchTerm,
|
|
12
|
+
}: TableEmptyStateProps) {
|
|
13
|
+
return (
|
|
14
|
+
<tr>
|
|
15
|
+
<td colSpan={colSpan} className="px-6 py-4 text-center">
|
|
16
|
+
{searchTerm
|
|
17
|
+
? <ITText as="span">No se encontraron resultados para tu búsqueda</ITText>
|
|
18
|
+
: <ITText as="span">No se encontraron resultados</ITText>}
|
|
19
|
+
</td>
|
|
20
|
+
</tr>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import TableHeaderCell from "./TableHeaderCell";
|
|
3
|
+
import { SearchColumn } from "../searchTable.props";
|
|
4
|
+
|
|
5
|
+
interface TableHeaderProps<T> {
|
|
6
|
+
columns: SearchColumn<T>[];
|
|
7
|
+
onSort: (key: string) => void;
|
|
8
|
+
sortConfig?: {
|
|
9
|
+
key: string;
|
|
10
|
+
direction: "asc" | "desc";
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function TableHeader<T>({
|
|
15
|
+
columns,
|
|
16
|
+
onSort,
|
|
17
|
+
sortConfig,
|
|
18
|
+
}: TableHeaderProps<T>) {
|
|
19
|
+
return (
|
|
20
|
+
<thead>
|
|
21
|
+
<tr className="bg-white border-b border-gray-200">
|
|
22
|
+
{columns.map((col) => (
|
|
23
|
+
<TableHeaderCell
|
|
24
|
+
key={col.key}
|
|
25
|
+
label={col.label}
|
|
26
|
+
sortable={col.sortable && col.type !== "actions"}
|
|
27
|
+
onSort={() => onSort(col.key)}
|
|
28
|
+
sortConfig={sortConfig?.key === col.key ? sortConfig : undefined}
|
|
29
|
+
className="px-3 py-3 text-center align-middle"
|
|
30
|
+
/>
|
|
31
|
+
))}
|
|
32
|
+
</tr>
|
|
33
|
+
</thead>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import SortButton from "./SortButton";
|
|
3
|
+
import ITText from "@/components/text/text";
|
|
4
|
+
|
|
5
|
+
interface TableHeaderCellProps {
|
|
6
|
+
label: string;
|
|
7
|
+
sortable?: boolean;
|
|
8
|
+
onSort: () => void;
|
|
9
|
+
sortConfig?: {
|
|
10
|
+
key: string;
|
|
11
|
+
direction: "asc" | "desc";
|
|
12
|
+
};
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function TableHeaderCell({
|
|
17
|
+
label,
|
|
18
|
+
sortable = false,
|
|
19
|
+
onSort,
|
|
20
|
+
sortConfig,
|
|
21
|
+
className = "",
|
|
22
|
+
}: TableHeaderCellProps) {
|
|
23
|
+
return (
|
|
24
|
+
<th className={className}>
|
|
25
|
+
<div className="flex flex-col items-center gap-2 w-full">
|
|
26
|
+
<div className="flex items-center justify-center gap-2 w-full">
|
|
27
|
+
<ITText as="span"
|
|
28
|
+
className="inline-block w-[96%] text-sm font-medium text-white bg-slate-400 px-3 py-1.5 pb-2 rounded-lg text-center leading-tight"
|
|
29
|
+
style={{
|
|
30
|
+
display: "-webkit-box",
|
|
31
|
+
WebkitBoxOrient: "vertical",
|
|
32
|
+
WebkitLineClamp:4,
|
|
33
|
+
overflow: "hidden",
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
{label}
|
|
37
|
+
</ITText>
|
|
38
|
+
{sortable && <SortButton sortConfig={sortConfig} onClick={onSort} />}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</th>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { useEditableRow } from "@/hooks/useEditableRow";
|
|
2
|
+
import { formatCurrencyMX } from "@/utils/table.utils";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { FaCheck, FaTimes } from "react-icons/fa";
|
|
6
|
+
import { SearchColumn } from "../searchTable.props";
|
|
7
|
+
import EditableCell from "./EditableCell";
|
|
8
|
+
import * as yup from "yup";
|
|
9
|
+
|
|
10
|
+
interface TableRowProps<T> {
|
|
11
|
+
row: T;
|
|
12
|
+
rowIndex: number;
|
|
13
|
+
columns: SearchColumn<T>[];
|
|
14
|
+
getNestedValue: (obj: unknown, path: string) => unknown;
|
|
15
|
+
onSave?: (row: T) => void;
|
|
16
|
+
onCancel?: () => void;
|
|
17
|
+
validationSchema?: yup.ObjectSchema<any>;
|
|
18
|
+
isEditing?: boolean;
|
|
19
|
+
onEdit?: (row: T) => void;
|
|
20
|
+
onDelete?: (row: T) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function TableRow<T>({
|
|
24
|
+
row,
|
|
25
|
+
rowIndex,
|
|
26
|
+
columns,
|
|
27
|
+
getNestedValue,
|
|
28
|
+
onSave,
|
|
29
|
+
onCancel,
|
|
30
|
+
validationSchema,
|
|
31
|
+
isEditing = false,
|
|
32
|
+
onEdit,
|
|
33
|
+
}: TableRowProps<T>) {
|
|
34
|
+
const {
|
|
35
|
+
editedRow,
|
|
36
|
+
errors,
|
|
37
|
+
isHovered,
|
|
38
|
+
setIsHovered,
|
|
39
|
+
hasErrors,
|
|
40
|
+
handleEdit,
|
|
41
|
+
handleSave,
|
|
42
|
+
handleCancel,
|
|
43
|
+
handleChange,
|
|
44
|
+
} = useEditableRow({ row, columns, getNestedValue, validationSchema });
|
|
45
|
+
|
|
46
|
+
const onEditAction = () => handleEdit(onEdit);
|
|
47
|
+
const onSaveAction = () => handleSave(onSave);
|
|
48
|
+
const onCancelAction = () => handleCancel(onCancel);
|
|
49
|
+
|
|
50
|
+
const renderCellContent = (col: SearchColumn<T>, rowData: T) => {
|
|
51
|
+
const value = getNestedValue(rowData, col.key);
|
|
52
|
+
|
|
53
|
+
if (col.render) return col.render(rowData);
|
|
54
|
+
|
|
55
|
+
switch (col.type) {
|
|
56
|
+
case "number":
|
|
57
|
+
return typeof value === "number" && col.currencyMX
|
|
58
|
+
? formatCurrencyMX(value)
|
|
59
|
+
: (value as React.ReactNode);
|
|
60
|
+
|
|
61
|
+
case "boolean":
|
|
62
|
+
return value ? (
|
|
63
|
+
<FaCheck
|
|
64
|
+
className="text-green-500"
|
|
65
|
+
aria-label="Verdadero"
|
|
66
|
+
title="Verdadero"
|
|
67
|
+
/>
|
|
68
|
+
) : (
|
|
69
|
+
<FaTimes className="text-red-500" aria-label="Falso" title="Falso" />
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
case "actions":
|
|
73
|
+
if (isEditing && col.saveActions) {
|
|
74
|
+
return col.saveActions(rowData, {
|
|
75
|
+
onSave: onSaveAction,
|
|
76
|
+
onCancel: onCancelAction,
|
|
77
|
+
hasErrors,
|
|
78
|
+
});
|
|
79
|
+
} else if (col.actions) {
|
|
80
|
+
return col.actions(rowData, { onEdit: onEditAction });
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
|
|
84
|
+
case "catalog":
|
|
85
|
+
if (col.catalogOptions) {
|
|
86
|
+
const catalogItem = col.catalogOptions.data.find(
|
|
87
|
+
(item) => item.id === value
|
|
88
|
+
);
|
|
89
|
+
return catalogItem?.name || (value as React.ReactNode);
|
|
90
|
+
}
|
|
91
|
+
return value as React.ReactNode;
|
|
92
|
+
|
|
93
|
+
default:
|
|
94
|
+
return value as React.ReactNode;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<tr
|
|
100
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
101
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
102
|
+
className={`border-b border-gray-200 transition-colors duration-150 ${
|
|
103
|
+
isEditing
|
|
104
|
+
? "bg-slate-50"
|
|
105
|
+
: rowIndex % 2 === 0
|
|
106
|
+
? "bg-white"
|
|
107
|
+
: "bg-gray-50"
|
|
108
|
+
} ${isHovered && !isEditing ? "bg-gray-100" : ""}`}
|
|
109
|
+
>
|
|
110
|
+
{columns.map((col) => (
|
|
111
|
+
<td
|
|
112
|
+
key={`${rowIndex}-${col.key}`}
|
|
113
|
+
className={clsx("pl-5 py-2", col.className, {
|
|
114
|
+
"text-center": col.type === "actions" || col.type === "boolean",
|
|
115
|
+
})}
|
|
116
|
+
>
|
|
117
|
+
{isEditing && col.editable ? (
|
|
118
|
+
<EditableCell
|
|
119
|
+
column={col}
|
|
120
|
+
value={getNestedValue(editedRow, col.key)}
|
|
121
|
+
onChange={(value) => handleChange(col.key, value)}
|
|
122
|
+
error={errors[col.key]}
|
|
123
|
+
row={editedRow}
|
|
124
|
+
/>
|
|
125
|
+
) : (
|
|
126
|
+
<div
|
|
127
|
+
className={clsx({
|
|
128
|
+
"flex flex-col items-center justify-center gap-1 ":
|
|
129
|
+
col.type === "actions",
|
|
130
|
+
})}
|
|
131
|
+
>
|
|
132
|
+
{
|
|
133
|
+
renderCellContent(
|
|
134
|
+
col,
|
|
135
|
+
isEditing ? editedRow : row
|
|
136
|
+
) as React.ReactNode
|
|
137
|
+
}
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
</td>
|
|
141
|
+
))}
|
|
142
|
+
</tr>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { TableSize, TableVariants } from "@/types/table.types";
|
|
2
|
+
import * as yup from "yup";
|
|
3
|
+
|
|
4
|
+
export type ColumnType = "string" | "date" | "number" | "boolean" | "actions" | "catalog";
|
|
5
|
+
|
|
6
|
+
export interface CatalogOption {
|
|
7
|
+
id: string | number;
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SearchColumn<T = any> {
|
|
12
|
+
key: string;
|
|
13
|
+
label: string;
|
|
14
|
+
type: "string" | "number" | "boolean" | "date" | "actions" | "catalog";
|
|
15
|
+
filter?: boolean | 'catalog';
|
|
16
|
+
sortable?: boolean;
|
|
17
|
+
editable?: boolean;
|
|
18
|
+
inputType?: "text" | "number" | "select" | "checkbox" | "date";
|
|
19
|
+
// OK: text number
|
|
20
|
+
// PENDING: select checkbox date
|
|
21
|
+
options?: { value: string | number; label: string }[];
|
|
22
|
+
validation?: (value: any, row?:any) => string | undefined;
|
|
23
|
+
className?: string;
|
|
24
|
+
currencyMX?: boolean;
|
|
25
|
+
catalogOptions?: {
|
|
26
|
+
data: Array<{ id: string | number; name: string }> | any[];
|
|
27
|
+
key?: string;
|
|
28
|
+
label?: string;
|
|
29
|
+
};
|
|
30
|
+
render?: (row: T) => React.ReactNode;
|
|
31
|
+
actions?: (row: T, helpers: { onEdit: (row: T) => void }) => React.ReactNode;
|
|
32
|
+
saveActions?: (row: T, helpers: { onSave: (row: T) => void, onCancel: () => void, hasErrors: any}) => React.ReactNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ITSearchTableProps<T> {
|
|
36
|
+
columns: SearchColumn<T>[];
|
|
37
|
+
containerClassName?: string;
|
|
38
|
+
searchInputPlaceholder?: string;
|
|
39
|
+
data: T[];
|
|
40
|
+
variant?: TableVariants;
|
|
41
|
+
className?: string;
|
|
42
|
+
size?: TableSize;
|
|
43
|
+
itemsPerPageOptions?: Array<number>;
|
|
44
|
+
defaultItemsPerPage?: number;
|
|
45
|
+
validationSchema?: yup.ObjectSchema<any>
|
|
46
|
+
title?: string;
|
|
47
|
+
pageIndex: number;
|
|
48
|
+
totalCount: number;
|
|
49
|
+
totalPages: number;
|
|
50
|
+
hasPreviousPage: boolean;
|
|
51
|
+
hasNextPage: boolean;
|
|
52
|
+
onPageChange?: (page: number) => void;
|
|
53
|
+
onItemsPerPageChange?: (itemsPerPage: number) => void;
|
|
54
|
+
onSortChange?: (sortConfig: { key: string, direction: "asc" | "desc" }) => void; // Se actualizó para incluir `key`
|
|
55
|
+
onFilterChange?: (filters: Record<string, string | boolean | number>) => void;
|
|
56
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useDebouncedSearch } from "@/hooks/useDebouncedSearch";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
|
|
5
|
+
import { sizeStyles, variantStyles } from "@/types/table.types";
|
|
6
|
+
import { ITSearchTableProps } from "./searchTable.props";
|
|
7
|
+
import SearchAndSortBar from "./components/SearchAndSortBar";
|
|
8
|
+
import TableHeader from "./components/TableHeader";
|
|
9
|
+
import TableRow from "./components/TableRow";
|
|
10
|
+
import { getNestedValue } from "@/utils/table.utils";
|
|
11
|
+
import TableEmptyState from "./components/TableEmptyState";
|
|
12
|
+
import PaginationControls from "./components/PaginationControls";
|
|
13
|
+
import ITText from "@/components/text/text";
|
|
14
|
+
|
|
15
|
+
interface CustomITSearchTableProps<T> extends ITSearchTableProps<T> {
|
|
16
|
+
editingRow?: number | null;
|
|
17
|
+
searchTermInitial?: string;
|
|
18
|
+
onClearSearch?: () => void;
|
|
19
|
+
onEdit?: (row: T, index: number) => void;
|
|
20
|
+
onSave?: (row: T, index: number) => void;
|
|
21
|
+
onCancel?: () => void;
|
|
22
|
+
sortConfig?: {
|
|
23
|
+
key: string;
|
|
24
|
+
direction: "asc" | "desc";
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function ITSearchTable<T extends Record<string, unknown>>({
|
|
29
|
+
columns,
|
|
30
|
+
data,
|
|
31
|
+
containerClassName,
|
|
32
|
+
searchTermInitial = "",
|
|
33
|
+
searchInputPlaceholder = "Buscar en todos los campos...",
|
|
34
|
+
className,
|
|
35
|
+
variant = "default",
|
|
36
|
+
size = "md",
|
|
37
|
+
itemsPerPageOptions = [10, 20, 50, 100],
|
|
38
|
+
defaultItemsPerPage = 10,
|
|
39
|
+
title,
|
|
40
|
+
pageIndex,
|
|
41
|
+
totalCount,
|
|
42
|
+
totalPages,
|
|
43
|
+
hasPreviousPage,
|
|
44
|
+
hasNextPage,
|
|
45
|
+
onPageChange,
|
|
46
|
+
onItemsPerPageChange,
|
|
47
|
+
onSortChange,
|
|
48
|
+
onFilterChange,
|
|
49
|
+
sortConfig,
|
|
50
|
+
editingRow = null,
|
|
51
|
+
validationSchema,
|
|
52
|
+
onClearSearch,
|
|
53
|
+
onEdit,
|
|
54
|
+
onSave,
|
|
55
|
+
onCancel,
|
|
56
|
+
}: CustomITSearchTableProps<T>) {
|
|
57
|
+
const [itemsPerPage, setItemsPerPage] = useState(defaultItemsPerPage);
|
|
58
|
+
|
|
59
|
+
const { searchTerm, handleSearchChange, handleClearSearch } = useDebouncedSearch({
|
|
60
|
+
initialValue: searchTermInitial,
|
|
61
|
+
onSearch: (value) => {
|
|
62
|
+
if (onFilterChange) {
|
|
63
|
+
onFilterChange(value ? { query: value } : {});
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const handleItemsPerPageChange = (value: number) => {
|
|
69
|
+
setItemsPerPage(value);
|
|
70
|
+
onItemsPerPageChange?.(value);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const goToPage = (page: number) => {
|
|
74
|
+
onPageChange?.(page);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleGlobalSort = () => {
|
|
78
|
+
const newDirection = sortConfig?.direction === "asc" ? "desc" : "asc";
|
|
79
|
+
onSortChange?.({ key: "id", direction: newDirection });
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleSort = (key: string) => {
|
|
83
|
+
const column = columns.find((col) => col.key === key);
|
|
84
|
+
if (!column || !column.sortable) return;
|
|
85
|
+
|
|
86
|
+
let newDirection: "asc" | "desc" = "asc";
|
|
87
|
+
if (sortConfig && sortConfig.key === key) {
|
|
88
|
+
newDirection = sortConfig.direction === "asc" ? "desc" : "asc";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
onSortChange?.({ key, direction: newDirection });
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleEdit = (row: T, index: number) => onEdit?.(row, index);
|
|
95
|
+
const handleSave = (updatedRow: T, index: number) => onSave?.(updatedRow, index);
|
|
96
|
+
const handleCancelEdit = () => onCancel?.();
|
|
97
|
+
|
|
98
|
+
const handleClearSearchWithClear = () => {
|
|
99
|
+
handleClearSearch();
|
|
100
|
+
onClearSearch?.();
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className={clsx("space-y-4 w-full", containerClassName)}>
|
|
105
|
+
{/* Card principal con sombra */}
|
|
106
|
+
<div className="shadow-md sm:rounded-lg overflow-hidden">
|
|
107
|
+
{/* Header fijo */}
|
|
108
|
+
{title && (
|
|
109
|
+
<div className="bg-teal-500 text-white px-6 py-4">
|
|
110
|
+
<ITText as="h2" className="text-xl font-bold text-center whitespace-nowrap">
|
|
111
|
+
{title}
|
|
112
|
+
</ITText>
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{/* Contenido con scroll */}
|
|
117
|
+
<div className="bg-white">
|
|
118
|
+
{/* Barra de búsqueda y ordenamiento */}
|
|
119
|
+
<div className="p-4 border-b border-gray-200">
|
|
120
|
+
<SearchAndSortBar
|
|
121
|
+
searchTerm={searchTerm}
|
|
122
|
+
onSearchChange={handleSearchChange}
|
|
123
|
+
onClearSearch={handleClearSearchWithClear}
|
|
124
|
+
onGlobalSort={handleGlobalSort}
|
|
125
|
+
sortConfig={sortConfig}
|
|
126
|
+
searchInputPlaceholder={searchInputPlaceholder}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Tabla con scroll horizontal */}
|
|
131
|
+
<div className="overflow-x-auto">
|
|
132
|
+
<table
|
|
133
|
+
className={clsx(
|
|
134
|
+
"min-w-full text-sm text-left bg-white text-gray-900 table-auto",
|
|
135
|
+
variantStyles[variant],
|
|
136
|
+
sizeStyles[size]
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
<TableHeader
|
|
140
|
+
columns={columns}
|
|
141
|
+
onSort={handleSort}
|
|
142
|
+
sortConfig={sortConfig}
|
|
143
|
+
/>
|
|
144
|
+
<tbody>
|
|
145
|
+
{data.length > 0 ? (
|
|
146
|
+
data.map((row, rowIndex) => (
|
|
147
|
+
<TableRow
|
|
148
|
+
key={rowIndex}
|
|
149
|
+
row={row}
|
|
150
|
+
rowIndex={rowIndex}
|
|
151
|
+
columns={columns}
|
|
152
|
+
validationSchema={validationSchema}
|
|
153
|
+
getNestedValue={getNestedValue}
|
|
154
|
+
isEditing={editingRow === rowIndex}
|
|
155
|
+
onEdit={() => handleEdit(row, rowIndex)}
|
|
156
|
+
onSave={(updatedRow) => handleSave(updatedRow, rowIndex)}
|
|
157
|
+
onCancel={handleCancelEdit}
|
|
158
|
+
/>
|
|
159
|
+
))
|
|
160
|
+
) : (
|
|
161
|
+
<TableEmptyState
|
|
162
|
+
colSpan={columns.length + 1}
|
|
163
|
+
searchTerm={searchTerm}
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
166
|
+
</tbody>
|
|
167
|
+
</table>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
{/* Paginación */}
|
|
173
|
+
<PaginationControls
|
|
174
|
+
pageIndex={pageIndex}
|
|
175
|
+
totalPages={totalPages}
|
|
176
|
+
hasPreviousPage={hasPreviousPage}
|
|
177
|
+
hasNextPage={hasNextPage}
|
|
178
|
+
onPageChange={goToPage}
|
|
179
|
+
itemsPerPage={itemsPerPage}
|
|
180
|
+
itemsPerPageOptions={itemsPerPageOptions}
|
|
181
|
+
onItemsPerPageChange={handleItemsPerPageChange}
|
|
182
|
+
currentCount={data.length}
|
|
183
|
+
totalCount={totalCount}
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type SegmentedControlSize = "sm" | "md";
|
|
4
|
+
|
|
5
|
+
export interface ISegmentedOption {
|
|
6
|
+
value: string;
|
|
7
|
+
label: string;
|
|
8
|
+
icon?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ITSegmentedControlProps {
|
|
12
|
+
options: ISegmentedOption[];
|
|
13
|
+
value: string;
|
|
14
|
+
onChange: (value: string) => void;
|
|
15
|
+
size?: SegmentedControlSize;
|
|
16
|
+
className?: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
}
|