@axzydev/axzy_ui_system 1.3.3 → 1.3.4

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.
@@ -1,4 +1,5 @@
1
1
  import React, { useState, useMemo, useCallback } from "react";
2
+ import { startOfDay, endOfDay } from "date-fns";
2
3
  import {
3
4
  ITTable,
4
5
  ITDataTable,
@@ -47,6 +48,74 @@ const virtualUserColumns: any = [
47
48
  { key: "active", label: "Active", type: "boolean", width: "12%" },
48
49
  ];
49
50
 
51
+ // Demo data for the new column filter modes: adds a `hiredAt` date so the
52
+ // same table can showcase both single-date and date-range filters.
53
+ const filterDemoData = TABLE_DATA.map((row, i) => ({
54
+ ...row,
55
+ hiredAt: new Date(2024, (i * 5) % 12, ((i * 7) % 27) + 1).toISOString(),
56
+ }));
57
+
58
+ // Columns exercising every new filter mode: ITSearchSelect (`"search"`) and
59
+ // ITDatePicker in single (`"date"`) and range (`"date-range"`) modes.
60
+ const filterDemoColumns: any = [
61
+ {
62
+ key: "name",
63
+ label: "Name",
64
+ type: "string",
65
+ sortable: true,
66
+ filter: true,
67
+ render: (row: Record<string, unknown>) => (
68
+ <span className="font-semibold text-slate-800 dark:text-white">{row.name as string}</span>
69
+ ),
70
+ },
71
+ {
72
+ key: "role",
73
+ label: "Role",
74
+ type: "string",
75
+ sortable: true,
76
+ filter: "search",
77
+ catalogOptions: {
78
+ data: [
79
+ { id: "Admin", name: "Admin" },
80
+ { id: "Editor", name: "Editor" },
81
+ { id: "Viewer", name: "Viewer" },
82
+ ],
83
+ },
84
+ render: (row: Record<string, unknown>) => (
85
+ <span className={`px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider rounded-md ${roleColor(row.role as string)}`}>
86
+ {row.role as string}
87
+ </span>
88
+ ),
89
+ },
90
+ {
91
+ key: "lastLogin",
92
+ label: "Last login",
93
+ type: "date",
94
+ sortable: true,
95
+ filter: "date",
96
+ render: (row: Record<string, unknown>) =>
97
+ new Date(row.lastLogin as string).toLocaleDateString("es-MX", {
98
+ day: "2-digit",
99
+ month: "short",
100
+ year: "numeric",
101
+ }),
102
+ },
103
+ {
104
+ key: "hiredAt",
105
+ label: "Hired",
106
+ type: "date",
107
+ sortable: true,
108
+ filter: "date-range",
109
+ dateFilterOptions: { maxDate: new Date() },
110
+ render: (row: Record<string, unknown>) =>
111
+ new Date(row.hiredAt as string).toLocaleDateString("es-MX", {
112
+ day: "2-digit",
113
+ month: "short",
114
+ year: "numeric",
115
+ }),
116
+ },
117
+ ];
118
+
50
119
  const nameColumn = {
51
120
  key: "name",
52
121
  label: "Name",
@@ -276,6 +345,9 @@ export const TableShowcase = () => {
276
345
  '<ITTable columns={columns} data={data} defaultView="cards" onRowClick={(row) => open(row)} />',
277
346
  '<ITTable columns={fixedColumns} data={data} layout="fixed" density="compact" />',
278
347
  '<ITTable columns={cols} data={rows} virtualized stickyHeader layout="fixed" density="compact" defaultItemsPerPage={300} itemsPerPageOptions={[50, 100, 300]} />',
348
+ '// ITSearchSelect typeahead (server-side search opcional vía catalogOptions.onSearch)\n{ key: "role", label: "Role", type: "string", filter: "search", catalogOptions: { data: roles } }',
349
+ '// ITDatePicker fecha única\n{ key: "lastLogin", label: "Last login", type: "date", filter: "date", dateFilterOptions: { maxDate: new Date() } }',
350
+ '// ITDatePicker rango\n{ key: "hiredAt", label: "Hired", type: "date", filter: "date-range", dateFilterOptions: { maxDate: new Date() } }',
279
351
  ],
280
352
  props: [
281
353
  { name: "columns", type: "Column<T>[]", description: "Definiciones de columna: key, label, type, sortable, filter, render, actions." },
@@ -309,6 +381,7 @@ export const TableShowcase = () => {
309
381
  "Virtualización: solo vista tabla; rinde con page size grande; filas de alto uniforme; el scroll no se reinicia al cambiar filtros.",
310
382
  "Virtualización exige alto de fila uniforme. El default es size-aware (getRowHeight(size, density)): con size=\"md\" el baseline es compact 33 / normal 45 / comfortable 53 (padding de celda + line box text-sm de 20px + 1px del divide-y); size lo desplaza por el line box (sm 16 / md 20 / lg 28 px, es decir −4 / 0 / +8 px). Una celda de dos líneas (avatar + texto) mide ~50px real, distinto de ese alto asumido, así que los spacers se desalinean: usar celdas de una sola línea o pasar un rowHeight explícito.",
311
383
  "ITSearchTable no soporta layout/density/autoCardBreakpoint/virtualized/width — no asumir paridad.",
384
+ "Nuevos modos de filtro por columna: filter: \"search\" renderiza ITSearchSelect (usa catalogOptions.data; opcional catalogOptions.onSearch para búsqueda en servidor), y filter: \"date\" / \"date-range\" renderizan ITDatePicker (bounds vía dateFilterOptions). El rango se guarda como [start, end] y se considera vacío cuando ambos extremos son null.",
312
385
  ],
313
386
  }}
314
387
  gallery={
@@ -440,6 +513,22 @@ export const TableShowcase = () => {
440
513
  />
441
514
  </div>
442
515
  </div>
516
+
517
+ <div>
518
+ <h4 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-3">Column filters: ITSearchSelect + ITDatePicker (single &amp; range)</h4>
519
+ <div className="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
520
+ <ITTable
521
+ columns={filterDemoColumns}
522
+ data={filterDemoData}
523
+ title="Filter demo"
524
+ size="sm"
525
+ defaultItemsPerPage={8}
526
+ />
527
+ </div>
528
+ <p className="mt-2 text-xs text-slate-500 dark:text-slate-400">
529
+ Role uses <code>filter: "search"</code> (ITSearchSelect). Last login uses <code>filter: "date"</code> and Hired uses <code>filter: "date-range"</code> (ITDatePicker), with <code>dateFilterOptions</code> bounds.
530
+ </p>
531
+ </div>
443
532
  </div>
444
533
  }
445
534
  />
@@ -475,8 +564,15 @@ export const DataTableShowcase = () => {
475
564
  key: "role",
476
565
  label: "Role",
477
566
  type: "string" as const,
478
- filter: true,
567
+ filter: "search" as const,
479
568
  sortable: true,
569
+ catalogOptions: {
570
+ data: [
571
+ { id: "Admin", name: "Admin" },
572
+ { id: "Editor", name: "Editor" },
573
+ { id: "Viewer", name: "Viewer" },
574
+ ],
575
+ },
480
576
  render: (row: any) => (
481
577
  <span className={`px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider rounded-md ${
482
578
  row.role === "Admin" ? "bg-primary-100 text-primary-700 dark:bg-primary-950/40 dark:text-primary-300" :
@@ -485,23 +581,42 @@ export const DataTableShowcase = () => {
485
581
  }`}>{row.role}</span>
486
582
  )
487
583
  },
584
+ {
585
+ key: "hiredAt",
586
+ label: "Hired",
587
+ type: "date" as const,
588
+ filter: "date-range" as const,
589
+ sortable: true,
590
+ dateFilterOptions: { maxDate: new Date() },
591
+ render: (row: any) =>
592
+ new Date(row.hiredAt as string).toLocaleDateString("es-MX", {
593
+ day: "2-digit",
594
+ month: "short",
595
+ year: "numeric",
596
+ }),
597
+ },
488
598
  { key: "active", label: "Active", type: "boolean" as const, filter: true }
489
599
  ], []);
490
600
 
491
- const MOCK_USERS = useMemo(() => [
492
- { id: 1, name: "Sofía Castillo", role: "Admin", active: true },
493
- { id: 2, name: "Daniela Klein", role: "Editor", active: true },
494
- { id: 3, name: "Mariana Reyes", role: "Viewer", active: false },
495
- { id: 4, name: "Camila Torres", role: "Admin", active: true },
496
- { id: 5, name: "Valentina Méndez", role: "Editor", active: true },
497
- { id: 6, name: "Luciana Rivas", role: "Viewer", active: false },
498
- { id: 7, name: "Isabella Cruz", role: "Editor", active: true },
499
- { id: 8, name: "Gabriela Vargas", role: "Admin", active: true },
500
- { id: 9, name: "Ana López", role: "Editor", active: false },
501
- { id: 10, name: "Carla Jiménez", role: "Viewer", active: true },
502
- { id: 11, name: "Luisa Fernández", role: "Admin", active: true },
503
- { id: 12, name: "Renata Morales", role: "Viewer", active: false },
504
- ], []);
601
+ const MOCK_USERS = useMemo(() =>
602
+ [
603
+ { id: 1, name: "Sofía Castillo", role: "Admin", active: true },
604
+ { id: 2, name: "Daniela Klein", role: "Editor", active: true },
605
+ { id: 3, name: "Mariana Reyes", role: "Viewer", active: false },
606
+ { id: 4, name: "Camila Torres", role: "Admin", active: true },
607
+ { id: 5, name: "Valentina Méndez", role: "Editor", active: true },
608
+ { id: 6, name: "Luciana Rivas", role: "Viewer", active: false },
609
+ { id: 7, name: "Isabella Cruz", role: "Editor", active: true },
610
+ { id: 8, name: "Gabriela Vargas", role: "Admin", active: true },
611
+ { id: 9, name: "Ana López", role: "Editor", active: false },
612
+ { id: 10, name: "Carla Jiménez", role: "Viewer", active: true },
613
+ { id: 11, name: "Luisa Fernández", role: "Admin", active: true },
614
+ { id: 12, name: "Renata Morales", role: "Viewer", active: false },
615
+ ].map((row, i) => ({
616
+ ...row,
617
+ hiredAt: new Date(2023, (i * 5) % 12, ((i * 7) % 27) + 1).toISOString(),
618
+ })),
619
+ []);
505
620
 
506
621
  const fetchData = useCallback(async (params: ITDataTableFetchParams): Promise<ITDataTableResponse<any>> => {
507
622
  const delay = apiState === "loading" ? 8000 : 400;
@@ -517,14 +632,31 @@ export const DataTableShowcase = () => {
517
632
 
518
633
  let mockData = [...MOCK_USERS];
519
634
 
635
+ const matchesFilter = (itemValue: unknown, filterVal: unknown): boolean => {
636
+ // Single date (filter: "date")
637
+ if (filterVal instanceof Date) {
638
+ const d = new Date(String(itemValue));
639
+ return !isNaN(d.getTime()) && d.toDateString() === filterVal.toDateString();
640
+ }
641
+ // Date range (filter: "date-range") — [start, end]
642
+ if (Array.isArray(filterVal)) {
643
+ const [start, end] = filterVal as [Date | null, Date | null];
644
+ if (!start && !end) return true;
645
+ const d = new Date(String(itemValue));
646
+ if (isNaN(d.getTime())) return false;
647
+ if (start && d.getTime() < startOfDay(start).getTime()) return false;
648
+ if (end && d.getTime() > endOfDay(end).getTime()) return false;
649
+ return true;
650
+ }
651
+ // Scalar filters (text/number/boolean/catalog/search id)
652
+ return String(itemValue).toLowerCase().includes(String(filterVal).toLowerCase());
653
+ };
654
+
520
655
  if (params.filters) {
521
656
  Object.keys(params.filters).forEach(key => {
522
657
  const filterVal = params.filters[key];
523
- if (filterVal) {
524
- mockData = mockData.filter(item =>
525
- String((item as any)[key]).toLowerCase().includes(String(filterVal).toLowerCase())
526
- );
527
- }
658
+ if (filterVal === undefined || filterVal === "") return;
659
+ mockData = mockData.filter(item => matchesFilter((item as any)[key], filterVal));
528
660
  });
529
661
  }
530
662
 
@@ -745,12 +877,14 @@ export const DataTableShowcase = () => {
745
877
  '<ITDataTable columns={fixedColumns} fetchData={api.fetchUsers} layout="fixed" density="compact" />',
746
878
  '<ITDataTable columns={cols} fetchData={api.page} virtualized stickyHeader defaultItemsPerPage={200} itemsPerPageOptions={[50, 100, 200]} debounceMs={0} />',
747
879
  '<ITDataTable columns={cols} fetchData={api.page} layout="fixed" density="compact" autoCardBreakpoint={640} />',
880
+ '// Filtro de columna tipo ITSearchSelect (typeahead)\n{ key: "role", label: "Role", type: "string", filter: "search", catalogOptions: { data: roles, onSearch: (q) => api.searchRoles(q) } }',
881
+ '// Filtro de columna tipo ITDatePicker (rango) — params.filters[key] llega como [start, end]\n{ key: "hiredAt", label: "Hired", type: "date", filter: "date-range", dateFilterOptions: { maxDate: new Date() } }',
748
882
  ],
749
883
  props: [
750
884
  { name: "columns", type: "Column<T>[]", description: "Definiciones de columna." },
751
885
  { name: "fetchData", type: "(params: ITDataTableFetchParams) => Promise<ITDataTableResponse<T>>", required: true, description: "Callback asíncrono disparado al cambiar página, filtros u orden. Retorna { data, total }." },
752
886
  { name: "debounceMs", type: "number", default: "500", description: "Espera antes de refetch tras cambiar filtros." },
753
- { name: "externalFilters", type: "Record<string, string | number | boolean | Date>", default: "{}", description: "Filtros externos fusionados con los internos." },
887
+ { name: "externalFilters", type: "ColumnFilters", default: "{}", description: "Filtros externos fusionados con los internos. Los valores de fecha pueden ser Date o [start, end]." },
754
888
  { name: "loadingIndicator", type: "ReactNode", description: "Elemento custom mostrado mientras carga." },
755
889
  { name: "fetchOnMount", type: "boolean", default: "true", description: "Dispara fetch al montar." },
756
890
  { name: "reloadTrigger", type: "number | string | boolean", description: "Cambiar su valor fuerza un refetch." },
@@ -787,6 +921,7 @@ export const DataTableShowcase = () => {
787
921
  "El toggle interno Table/Cards también desactiva la virtualización; las cards nunca se virtualizan.",
788
922
  "debounceMs={0} evita el retardo de refetch en demos.",
789
923
  "variant=\"minimal\" se acepta por tipo pero no tiene estilos (igual que default). ITSearchTable no soporta layout/density/autoCardBreakpoint/virtualized/width.",
924
+ "Modos de filtro por columna: filter: \"search\" renderiza ITSearchSelect (usa catalogOptions.data; catalogOptions.onSearch habilita búsqueda server-side debounced), y filter: \"date\" / \"date-range\" renderizan ITDatePicker con dateFilterOptions { minDate, maxDate }. En fetchData, params.filters[key] llega como Date (date) o [start, end] (date-range).",
790
925
  ],
791
926
  }}
792
927
  gallery={
@@ -1,5 +1,22 @@
1
1
  export type TableVariants = "default" | "striped" | "bordered";
2
2
 
3
+ /**
4
+ * Value stored for a single column filter.
5
+ *
6
+ * - `string | number | boolean` — text/number/boolean filters and catalog ids.
7
+ * - `Date` — a single-date filter (`Column.filter: "date"`).
8
+ * - `[Date | null, Date | null]` — a date-range filter (`Column.filter: "date-range"`).
9
+ */
10
+ export type ColumnFilterValue =
11
+ | string
12
+ | number
13
+ | boolean
14
+ | Date
15
+ | [Date | null, Date | null];
16
+
17
+ /** Map of column `key` to its active filter value. */
18
+ export type ColumnFilters = Record<string, ColumnFilterValue>;
19
+
3
20
  export type TableSize = "sm" | "md" | "lg";
4
21
 
5
22
  export const variantStyles: Record<TableVariants, string> = {