@sincpro/web-ui 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +157 -2
- package/dist/components/assist/AssistMark.d.ts +64 -0
- package/dist/components/assist/AssistMark.js +108 -0
- package/dist/components/assist/AssistPending.d.ts +24 -0
- package/dist/components/assist/AssistPending.js +26 -0
- package/dist/components/assist/AssistSuggestion.d.ts +42 -0
- package/dist/components/assist/AssistSuggestion.js +55 -0
- package/dist/components/assist/ChangeReview.d.ts +56 -0
- package/dist/components/assist/ChangeReview.js +121 -0
- package/dist/components/assist/assist.d.ts +77 -0
- package/dist/components/assist/assist.js +87 -0
- package/dist/components/assist/index.d.ts +5 -0
- package/dist/components/assist/index.js +7 -0
- package/dist/components/chatter/Chatter.d.ts +161 -0
- package/dist/components/chatter/Chatter.js +232 -0
- package/dist/components/chatter/Messages.d.ts +66 -0
- package/dist/components/chatter/Messages.js +43 -0
- package/dist/components/chatter/Timeline.d.ts +21 -0
- package/dist/components/chatter/Timeline.js +30 -0
- package/dist/components/chatter/index.d.ts +3 -0
- package/dist/components/chatter/index.js +4 -0
- package/dist/components/form/Form.d.ts +52 -0
- package/dist/components/form/Form.js +49 -0
- package/dist/components/form/Sheet.d.ts +43 -0
- package/dist/components/form/Sheet.js +83 -0
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.js +4 -0
- package/dist/components/frame/Chart.d.ts +13 -0
- package/dist/components/frame/Chart.js +24 -0
- package/dist/components/frame/FrameView.d.ts +33 -0
- package/dist/components/frame/FrameView.js +125 -0
- package/dist/components/frame/index.d.ts +3 -0
- package/dist/components/frame/index.js +3 -0
- package/dist/components/frame/model.d.ts +43 -0
- package/dist/components/frame/model.js +101 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.js +11 -0
- package/dist/components/lines/LineItems.d.ts +74 -0
- package/dist/components/lines/LineItems.js +229 -0
- package/dist/components/lines/index.d.ts +1 -0
- package/dist/components/lines/index.js +2 -0
- package/dist/components/lines/lines.d.ts +23 -0
- package/dist/components/lines/lines.js +26 -0
- package/dist/components/listing/ColumnsMenu.d.ts +20 -0
- package/dist/components/listing/ColumnsMenu.js +46 -0
- package/dist/components/listing/GroupingLevels.d.ts +35 -0
- package/dist/components/listing/GroupingLevels.js +45 -0
- package/dist/components/listing/Listing.d.ts +27 -0
- package/dist/components/listing/Listing.js +86 -0
- package/dist/components/listing/ListingBar.d.ts +73 -0
- package/dist/components/listing/ListingBar.js +105 -0
- package/dist/components/listing/Pagination.d.ts +36 -0
- package/dist/components/listing/Pagination.js +69 -0
- package/dist/components/listing/RecordCard.d.ts +33 -0
- package/dist/components/listing/RecordCard.js +22 -0
- package/dist/components/listing/RecordLink.d.ts +7 -0
- package/dist/components/listing/RecordLink.js +22 -0
- package/dist/components/listing/Row.d.ts +106 -0
- package/dist/components/listing/Row.js +122 -0
- package/dist/components/listing/SortMenu.d.ts +62 -0
- package/dist/components/listing/SortMenu.js +105 -0
- package/dist/components/listing/ViewSwitcher.d.ts +24 -0
- package/dist/components/listing/ViewSwitcher.js +16 -0
- package/dist/components/listing/columns.d.ts +47 -0
- package/dist/components/listing/columns.js +72 -0
- package/dist/components/listing/grouping.d.ts +35 -0
- package/dist/components/listing/grouping.js +47 -0
- package/dist/components/listing/index.d.ts +17 -0
- package/dist/components/listing/index.js +20 -0
- package/dist/components/listing/modelColumns.d.ts +50 -0
- package/dist/components/listing/modelColumns.js +103 -0
- package/dist/components/listing/pages.d.ts +78 -0
- package/dist/components/listing/pages.js +135 -0
- package/dist/components/listing/selection.d.ts +21 -0
- package/dist/components/listing/selection.js +43 -0
- package/dist/components/listing/set.d.ts +48 -0
- package/dist/components/listing/set.js +24 -0
- package/dist/components/listing/tree.d.ts +28 -0
- package/dist/components/listing/tree.js +23 -0
- package/dist/components/navigation/CommandPalette.d.ts +40 -0
- package/dist/components/navigation/CommandPalette.js +114 -0
- package/dist/components/navigation/NavigationTree.d.ts +31 -0
- package/dist/components/navigation/NavigationTree.js +79 -0
- package/dist/components/navigation/commands.d.ts +30 -0
- package/dist/components/navigation/commands.js +30 -0
- package/dist/components/navigation/index.d.ts +3 -0
- package/dist/components/navigation/index.js +4 -0
- package/dist/components/record/Breadcrumb.d.ts +42 -0
- package/dist/components/record/Breadcrumb.js +98 -0
- package/dist/components/record/DirtyActions.d.ts +26 -0
- package/dist/components/record/DirtyActions.js +22 -0
- package/dist/components/record/Pager.d.ts +18 -0
- package/dist/components/record/Pager.js +33 -0
- package/dist/components/record/RecordHeader.d.ts +71 -0
- package/dist/components/record/RecordHeader.js +72 -0
- package/dist/components/record/RecordSkeleton.d.ts +3 -0
- package/dist/components/record/RecordSkeleton.js +21 -0
- package/dist/components/record/SmartButtons.d.ts +35 -0
- package/dist/components/record/SmartButtons.js +38 -0
- package/dist/components/record/StatusBar.d.ts +51 -0
- package/dist/components/record/StatusBar.js +168 -0
- package/dist/components/record/ThumbBar.d.ts +36 -0
- package/dist/components/record/ThumbBar.js +86 -0
- package/dist/components/record/index.d.ts +6 -0
- package/dist/components/record/index.js +8 -0
- package/dist/components/search/CriteriaBuilder.d.ts +58 -0
- package/dist/components/search/CriteriaBuilder.js +278 -0
- package/dist/components/search/CriteriaChips.d.ts +22 -0
- package/dist/components/search/CriteriaChips.js +64 -0
- package/dist/components/search/FilterBuilder.d.ts +142 -0
- package/dist/components/search/FilterBuilder.js +421 -0
- package/dist/components/search/JoinWord.d.ts +8 -0
- package/dist/components/search/JoinWord.js +47 -0
- package/dist/components/search/JsonEditor.d.ts +19 -0
- package/dist/components/search/JsonEditor.js +78 -0
- package/dist/components/search/SearchBar.d.ts +78 -0
- package/dist/components/search/SearchBar.js +387 -0
- package/dist/components/search/criteria.d.ts +116 -0
- package/dist/components/search/criteria.js +159 -0
- package/dist/components/search/datePeriods.d.ts +122 -0
- package/dist/components/search/datePeriods.js +134 -0
- package/dist/components/search/dateRanges.d.ts +54 -0
- package/dist/components/search/dateRanges.js +89 -0
- package/dist/components/search/index.d.ts +9 -0
- package/dist/components/search/index.js +20 -0
- package/dist/components/search/model.d.ts +54 -0
- package/dist/components/search/model.js +37 -0
- package/dist/components/search/query.d.ts +166 -0
- package/dist/components/search/query.js +436 -0
- package/dist/components/shell/AppFrame.d.ts +14 -0
- package/dist/components/shell/AppFrame.js +80 -0
- package/dist/components/shell/TopBar.d.ts +73 -0
- package/dist/components/shell/TopBar.js +54 -0
- package/dist/components/shell/headerSlot.d.ts +17 -0
- package/dist/components/shell/headerSlot.js +21 -0
- package/dist/components/shell/index.d.ts +2 -0
- package/dist/components/shell/index.js +3 -0
- package/dist/foundations/aria.d.ts +14 -0
- package/dist/foundations/aria.js +18 -0
- package/dist/foundations/calendar.d.ts +28 -0
- package/dist/foundations/calendar.js +183 -0
- package/dist/foundations/copy.d.ts +619 -0
- package/dist/foundations/copy.es.d.ts +2 -0
- package/dist/foundations/copy.es.js +377 -0
- package/dist/foundations/copy.js +472 -0
- package/dist/foundations/density.d.ts +16 -0
- package/dist/foundations/density.js +21 -0
- package/dist/foundations/grouping.d.ts +281 -0
- package/dist/foundations/grouping.js +0 -0
- package/dist/foundations/index.d.ts +14 -0
- package/dist/foundations/index.js +30 -0
- package/dist/foundations/layout.d.ts +9 -0
- package/dist/foundations/layout.js +9 -0
- package/dist/foundations/pivot.d.ts +222 -0
- package/dist/foundations/pivot.js +198 -0
- package/dist/foundations/pivotSheet.d.ts +61 -0
- package/dist/foundations/pivotSheet.js +0 -0
- package/dist/foundations/slots.d.ts +67 -0
- package/dist/foundations/slots.js +63 -0
- package/dist/foundations/sticky.d.ts +53 -0
- package/dist/foundations/sticky.js +150 -0
- package/dist/foundations/surface.d.ts +9 -0
- package/dist/foundations/surface.js +26 -0
- package/dist/foundations/theme.d.ts +42 -0
- package/dist/foundations/theme.js +88 -0
- package/dist/foundations/types.d.ts +23 -0
- package/dist/foundations/types.js +50 -0
- package/dist/foundations/variants.d.ts +19 -0
- package/dist/foundations/variants.js +48 -0
- package/dist/foundations/xlsx.d.ts +86 -0
- package/dist/foundations/xlsx.js +0 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +19 -0
- package/dist/primitives/block/Block.d.ts +66 -0
- package/dist/primitives/block/Block.js +34 -0
- package/dist/primitives/block/Line.d.ts +18 -0
- package/dist/primitives/block/Line.js +45 -0
- package/dist/primitives/block/SectionGrid.d.ts +30 -0
- package/dist/primitives/block/SectionGrid.js +29 -0
- package/dist/primitives/block/Steps.d.ts +76 -0
- package/dist/primitives/block/Steps.js +43 -0
- package/dist/primitives/block/index.d.ts +4 -0
- package/dist/primitives/block/index.js +7 -0
- package/dist/primitives/button/Button.d.ts +137 -0
- package/dist/primitives/button/Button.js +234 -0
- package/dist/primitives/button/index.d.ts +1 -0
- package/dist/primitives/button/index.js +2 -0
- package/dist/primitives/cell/CellStack.d.ts +47 -0
- package/dist/primitives/cell/CellStack.js +33 -0
- package/dist/primitives/cell/index.d.ts +1 -0
- package/dist/primitives/cell/index.js +3 -0
- package/dist/primitives/choice/ComboBox.d.ts +16 -0
- package/dist/primitives/choice/ComboBox.js +52 -0
- package/dist/primitives/choice/RadioGroup.d.ts +18 -0
- package/dist/primitives/choice/RadioGroup.js +50 -0
- package/dist/primitives/choice/SearchField.d.ts +24 -0
- package/dist/primitives/choice/SearchField.js +27 -0
- package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
- package/dist/primitives/choice/SegmentedControl.js +69 -0
- package/dist/primitives/choice/Slider.d.ts +16 -0
- package/dist/primitives/choice/Slider.js +46 -0
- package/dist/primitives/choice/Switch.d.ts +14 -0
- package/dist/primitives/choice/Switch.js +65 -0
- package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
- package/dist/primitives/choice/ThemeSwitcher.js +25 -0
- package/dist/primitives/choice/index.d.ts +8 -0
- package/dist/primitives/choice/index.js +11 -0
- package/dist/primitives/choice/options.d.ts +28 -0
- package/dist/primitives/choice/options.js +6 -0
- package/dist/primitives/date/Calendar.d.ts +31 -0
- package/dist/primitives/date/Calendar.js +110 -0
- package/dist/primitives/date/DatePicker.d.ts +34 -0
- package/dist/primitives/date/DatePicker.js +99 -0
- package/dist/primitives/date/index.d.ts +2 -0
- package/dist/primitives/date/index.js +4 -0
- package/dist/primitives/dialog/Dialog.d.ts +118 -0
- package/dist/primitives/dialog/Dialog.js +121 -0
- package/dist/primitives/dialog/index.d.ts +1 -0
- package/dist/primitives/dialog/index.js +2 -0
- package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
- package/dist/primitives/disclosure/Disclosure.js +52 -0
- package/dist/primitives/disclosure/index.d.ts +1 -0
- package/dist/primitives/disclosure/index.js +2 -0
- package/dist/primitives/field/Checks.d.ts +57 -0
- package/dist/primitives/field/Checks.js +72 -0
- package/dist/primitives/field/DropZone.d.ts +42 -0
- package/dist/primitives/field/DropZone.js +52 -0
- package/dist/primitives/field/Field.d.ts +166 -0
- package/dist/primitives/field/Field.js +240 -0
- package/dist/primitives/field/Suggest.d.ts +48 -0
- package/dist/primitives/field/Suggest.js +43 -0
- package/dist/primitives/field/aria.d.ts +61 -0
- package/dist/primitives/field/aria.js +70 -0
- package/dist/primitives/field/context.d.ts +30 -0
- package/dist/primitives/field/context.js +13 -0
- package/dist/primitives/field/index.d.ts +7 -0
- package/dist/primitives/field/index.js +11 -0
- package/dist/primitives/field/layout.d.ts +24 -0
- package/dist/primitives/field/layout.js +10 -0
- package/dist/primitives/field/mode.d.ts +9 -0
- package/dist/primitives/field/mode.js +13 -0
- package/dist/primitives/hint/Hint.d.ts +31 -0
- package/dist/primitives/hint/Hint.js +42 -0
- package/dist/primitives/hint/index.d.ts +1 -0
- package/dist/primitives/hint/index.js +1 -0
- package/dist/primitives/index.d.ts +19 -0
- package/dist/primitives/index.js +20 -0
- package/dist/primitives/menu/Menu.d.ts +100 -0
- package/dist/primitives/menu/Menu.js +59 -0
- package/dist/primitives/menu/index.d.ts +1 -0
- package/dist/primitives/menu/index.js +2 -0
- package/dist/primitives/notice/Callout.d.ts +49 -0
- package/dist/primitives/notice/Callout.js +28 -0
- package/dist/primitives/notice/Notice.d.ts +45 -0
- package/dist/primitives/notice/Notice.js +61 -0
- package/dist/primitives/notice/Toast.d.ts +55 -0
- package/dist/primitives/notice/Toast.js +142 -0
- package/dist/primitives/notice/index.d.ts +3 -0
- package/dist/primitives/notice/index.js +5 -0
- package/dist/primitives/overlay/Popover.d.ts +6 -0
- package/dist/primitives/overlay/Popover.js +27 -0
- package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
- package/dist/primitives/overlay/PopoverTrigger.js +38 -0
- package/dist/primitives/overlay/Tooltip.d.ts +38 -0
- package/dist/primitives/overlay/Tooltip.js +74 -0
- package/dist/primitives/overlay/index.d.ts +3 -0
- package/dist/primitives/overlay/index.js +5 -0
- package/dist/primitives/picker/Picker.d.ts +48 -0
- package/dist/primitives/picker/Picker.js +150 -0
- package/dist/primitives/picker/index.d.ts +1 -0
- package/dist/primitives/picker/index.js +2 -0
- package/dist/primitives/surface/Peek.d.ts +60 -0
- package/dist/primitives/surface/Peek.js +71 -0
- package/dist/primitives/surface/index.d.ts +1 -0
- package/dist/primitives/surface/index.js +2 -0
- package/dist/primitives/table/CellEditor.d.ts +14 -0
- package/dist/primitives/table/CellEditor.js +83 -0
- package/dist/primitives/table/DataTable.d.ts +135 -0
- package/dist/primitives/table/DataTable.js +244 -0
- package/dist/primitives/table/EditableTable.d.ts +96 -0
- package/dist/primitives/table/EditableTable.js +423 -0
- package/dist/primitives/table/PivotTable.d.ts +14 -0
- package/dist/primitives/table/PivotTable.js +0 -0
- package/dist/primitives/table/ReportTable.d.ts +38 -0
- package/dist/primitives/table/ReportTable.js +106 -0
- package/dist/primitives/table/RowActions.d.ts +16 -0
- package/dist/primitives/table/RowActions.js +28 -0
- package/dist/primitives/table/SheetTable.d.ts +70 -0
- package/dist/primitives/table/SheetTable.js +197 -0
- package/dist/primitives/table/Table.d.ts +106 -0
- package/dist/primitives/table/Table.js +116 -0
- package/dist/primitives/table/index.d.ts +7 -0
- package/dist/primitives/table/index.js +9 -0
- package/dist/primitives/tabs/Tabs.d.ts +91 -0
- package/dist/primitives/tabs/Tabs.js +56 -0
- package/dist/primitives/tabs/index.d.ts +1 -0
- package/dist/primitives/tabs/index.js +1 -0
- package/dist/primitives/tag/Tag.d.ts +36 -0
- package/dist/primitives/tag/Tag.js +68 -0
- package/dist/primitives/tag/Tags.d.ts +57 -0
- package/dist/primitives/tag/Tags.js +104 -0
- package/dist/primitives/tag/TagsField.d.ts +32 -0
- package/dist/primitives/tag/TagsField.js +182 -0
- package/dist/primitives/tag/choices.d.ts +38 -0
- package/dist/primitives/tag/choices.js +26 -0
- package/dist/primitives/tag/index.d.ts +4 -0
- package/dist/primitives/tag/index.js +8 -0
- package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
- package/dist/primitives/toolbar/Toolbar.js +36 -0
- package/dist/primitives/toolbar/index.d.ts +1 -0
- package/dist/primitives/toolbar/index.js +2 -0
- package/dist/primitives/value/Copyable.d.ts +37 -0
- package/dist/primitives/value/Copyable.js +50 -0
- package/dist/primitives/value/Fecha.d.ts +12 -0
- package/dist/primitives/value/Fecha.js +80 -0
- package/dist/primitives/value/Meter.d.ts +29 -0
- package/dist/primitives/value/Meter.js +46 -0
- package/dist/primitives/value/Metric.d.ts +42 -0
- package/dist/primitives/value/Metric.js +38 -0
- package/dist/primitives/value/Person.d.ts +90 -0
- package/dist/primitives/value/Person.js +208 -0
- package/dist/primitives/value/Relation.d.ts +69 -0
- package/dist/primitives/value/Relation.js +57 -0
- package/dist/primitives/value/Totals.d.ts +26 -0
- package/dist/primitives/value/Totals.js +4 -0
- package/dist/primitives/value/Value.d.ts +77 -0
- package/dist/primitives/value/Value.js +119 -0
- package/dist/primitives/value/index.d.ts +8 -0
- package/dist/primitives/value/index.js +9 -0
- package/dist/tokens.css +1025 -0
- package/dist/views/index.d.ts +2 -0
- package/dist/views/index.js +3 -0
- package/dist/views/list/List.d.ts +113 -0
- package/dist/views/list/List.js +97 -0
- package/dist/views/list/ListView.d.ts +6 -0
- package/dist/views/list/ListView.js +269 -0
- package/dist/views/list/columns.d.ts +18 -0
- package/dist/views/list/columns.js +27 -0
- package/dist/views/list/derive.d.ts +76 -0
- package/dist/views/list/derive.js +181 -0
- package/dist/views/list/index.d.ts +2 -0
- package/dist/views/list/index.js +4 -0
- package/dist/views/list/presentations.d.ts +39 -0
- package/dist/views/list/presentations.js +103 -0
- package/dist/views/list/types.d.ts +272 -0
- package/dist/views/list/types.js +1 -0
- package/dist/views/list/useNarrowList.d.ts +17 -0
- package/dist/views/list/useNarrowList.js +32 -0
- package/dist/views/record/Record.d.ts +126 -0
- package/dist/views/record/Record.js +141 -0
- package/dist/views/record/index.d.ts +1 -0
- package/dist/views/record/index.js +2 -0
- package/package.json +109 -4
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { Menu } from "../../primitives/menu/Menu.js";
|
|
5
|
+
import { ArrowDownUp, ArrowDownWideNarrow, ArrowUpNarrowWide } from "lucide-react";
|
|
6
|
+
/**
|
|
7
|
+
* Las filas, ordenadas acá.
|
|
8
|
+
*
|
|
9
|
+
* Sólo cuando el campo activo declara `value`; si no, se devuelven tal cual —
|
|
10
|
+
* ordenar una página de un conjunto que se pagina afuera produce un orden que es
|
|
11
|
+
* mentira, porque ordena las doce que llegaron y no las ciento ochenta y siete.
|
|
12
|
+
*
|
|
13
|
+
* Los faltantes van SIEMPRE al final, suba o baje el orden. Es lo que hace
|
|
14
|
+
* cualquier planilla, y la razón es que un vacío no es «el más chico»: es la
|
|
15
|
+
* ausencia de respuesta, y ponerlo primero al invertir el orden llena la primera
|
|
16
|
+
* pantalla con las filas que menos dicen.
|
|
17
|
+
*/
|
|
18
|
+
export function sortItems(items, fields, sort) {
|
|
19
|
+
if (sort === null || sort === undefined)
|
|
20
|
+
return items;
|
|
21
|
+
const field = fields.find((one) => one.field === sort.key);
|
|
22
|
+
if (field?.value === undefined)
|
|
23
|
+
return items;
|
|
24
|
+
const read = field.value;
|
|
25
|
+
const sign = sort.direction === "asc" ? 1 : -1;
|
|
26
|
+
return [...items].sort((a, b) => {
|
|
27
|
+
const left = read(a);
|
|
28
|
+
const right = read(b);
|
|
29
|
+
const leftMissing = left === null || left === undefined || left === "";
|
|
30
|
+
const rightMissing = right === null || right === undefined || right === "";
|
|
31
|
+
if (leftMissing && rightMissing)
|
|
32
|
+
return 0;
|
|
33
|
+
if (leftMissing)
|
|
34
|
+
return 1;
|
|
35
|
+
if (rightMissing)
|
|
36
|
+
return -1;
|
|
37
|
+
if (typeof left === "number" && typeof right === "number")
|
|
38
|
+
return (left - right) * sign;
|
|
39
|
+
return String(left).localeCompare(String(right)) * sign;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function SortMenu({ fields, sort, onSortChange, surface }) {
|
|
43
|
+
const copy = useUiCopy();
|
|
44
|
+
if (fields.length === 0)
|
|
45
|
+
return null;
|
|
46
|
+
const current = fields.find((one) => one.field === sort?.key);
|
|
47
|
+
const groups = [
|
|
48
|
+
{
|
|
49
|
+
label: copy.sorting.by,
|
|
50
|
+
options: fields.map((one) => ({
|
|
51
|
+
id: one.field,
|
|
52
|
+
label: one.label,
|
|
53
|
+
checked: one.field === sort?.key,
|
|
54
|
+
})),
|
|
55
|
+
},
|
|
56
|
+
/* La dirección sólo cuando hay algo ordenado: «descendente» sin campo no
|
|
57
|
+
ordena nada, y una opción que no hace nada es una opción que se prueba. */
|
|
58
|
+
...(current === undefined
|
|
59
|
+
? []
|
|
60
|
+
: [
|
|
61
|
+
{
|
|
62
|
+
options: [
|
|
63
|
+
{
|
|
64
|
+
id: "__asc__",
|
|
65
|
+
label: current.ascLabel ?? copy.sorting.asc,
|
|
66
|
+
icon: ArrowUpNarrowWide,
|
|
67
|
+
checked: sort?.direction === "asc",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "__desc__",
|
|
71
|
+
label: current.descLabel ?? copy.sorting.desc,
|
|
72
|
+
icon: ArrowDownWideNarrow,
|
|
73
|
+
checked: sort?.direction === "desc",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
{ options: [{ id: "__none__", label: copy.sorting.clear }] },
|
|
78
|
+
]),
|
|
79
|
+
];
|
|
80
|
+
return (_jsx(Menu, { label: current === undefined ? copy.sorting.label : current.label, icon: sort === null || sort === undefined
|
|
81
|
+
? ArrowDownUp
|
|
82
|
+
: sort.direction === "asc"
|
|
83
|
+
? ArrowUpNarrowWide
|
|
84
|
+
: ArrowDownWideNarrow, groups: groups, active: sort !== null && sort !== undefined, size: "sm", width: "sm", closeOnSelect: false, ...(surface === undefined ? {} : { surface }), onSelect: (id) => {
|
|
85
|
+
if (id === "__none__")
|
|
86
|
+
return onSortChange(null);
|
|
87
|
+
if (id === "__asc__") {
|
|
88
|
+
return onSortChange(sort === null ? null : { ...sort, direction: "asc" });
|
|
89
|
+
}
|
|
90
|
+
if (id === "__desc__") {
|
|
91
|
+
return onSortChange(sort === null ? null : { ...sort, direction: "desc" });
|
|
92
|
+
}
|
|
93
|
+
/* Volver a elegir el campo que ya está puesto DA VUELTA la dirección, que
|
|
94
|
+
es lo que hace el clic en un encabezado. Sin eso, invertir el orden son
|
|
95
|
+
dos gestos en un menú y uno en la tabla — la misma capacidad costando
|
|
96
|
+
distinto según la presentación. */
|
|
97
|
+
if (sort?.key === id) {
|
|
98
|
+
return onSortChange({
|
|
99
|
+
key: id,
|
|
100
|
+
direction: sort.direction === "asc" ? "desc" : "asc",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
onSortChange({ key: id, direction: "asc" });
|
|
104
|
+
} }));
|
|
105
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Surface } from "../../foundations/types.js";
|
|
2
|
+
import type { LucideIcon } from "lucide-react";
|
|
3
|
+
/** How the same list is shown: table, board, timeline.
|
|
4
|
+
*
|
|
5
|
+
* The icons go together and touching —a segmented group and not three loose
|
|
6
|
+
* buttons— because they are MUTUALLY EXCLUSIVE options of a single question.
|
|
7
|
+
* Three separate buttons read as three different actions, and then it is not
|
|
8
|
+
* understood that choosing one turns the other off.
|
|
9
|
+
*
|
|
10
|
+
* The label goes in the `title` and in the `aria-label`, never drawn: they are
|
|
11
|
+
* three silhouettes that are learned once and recognized afterwards without
|
|
12
|
+
* reading, and writing them out would burn the width the search box needs. */
|
|
13
|
+
export interface ViewOption {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
icon: LucideIcon;
|
|
17
|
+
}
|
|
18
|
+
export declare function ViewSwitcher({ options, current, onSelect, surface, }: {
|
|
19
|
+
options: readonly ViewOption[];
|
|
20
|
+
current: string;
|
|
21
|
+
onSelect?: (id: string) => void;
|
|
22
|
+
/** By default it inherits it from the surface that wraps it. */
|
|
23
|
+
surface?: Surface;
|
|
24
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { bySurface, useSurface } from "../../foundations/surface.js";
|
|
5
|
+
export function ViewSwitcher({ options, current, onSelect, surface, }) {
|
|
6
|
+
const activeSurface = useSurface(surface);
|
|
7
|
+
const copy = useUiCopy();
|
|
8
|
+
const rule = bySurface(activeSurface, "border-line-strong", "border-chrome-ink/45");
|
|
9
|
+
return (_jsx("div", { role: "group", "aria-label": copy.present.group, className: `flex shrink-0 items-center overflow-hidden rounded-control border ${rule}`, children: options.map((o, i) => {
|
|
10
|
+
const here = o.id === current;
|
|
11
|
+
const Icon = o.icon;
|
|
12
|
+
return (_jsx("button", { type: "button", onClick: () => onSelect?.(o.id), "aria-pressed": here, title: o.label, "aria-label": o.label, className: `inline-flex size-(--size-control-sm) items-center justify-center transition-colors duration-(--duration-instant) focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-primary ${i === 0 ? "" : `border-l ${rule}`} ${here
|
|
13
|
+
? bySurface(activeSurface, "bg-ink/8 text-ink", "bg-chrome-link/20 text-chrome-link")
|
|
14
|
+
: bySurface(activeSurface, "bg-surface text-muted hover:bg-ink/5 hover:text-ink", "bg-chrome-ink/6 text-chrome-muted hover:bg-chrome-ink/14 hover:text-chrome-ink")}`, children: _jsx(Icon, { className: "size-(--size-icon-md)", strokeWidth: 1.75 }) }, o.id));
|
|
15
|
+
}) }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which FIELDS of a set are on screen.
|
|
3
|
+
*
|
|
4
|
+
* Odoo lists split columns in two: fields without `optional` cannot leave the
|
|
5
|
+
* view; fields with `optional="show"|"hide"` appear in the columns submenu
|
|
6
|
+
* and can be added or removed. ListView and FrameView were inventing that
|
|
7
|
+
* menu each on their own — FrameView let every column hide, ListView had no
|
|
8
|
+
* menu at all — so the same question («may I hide this?») had two answers.
|
|
9
|
+
*
|
|
10
|
+
* This hook is the third knob of a SET, next to `useSetControl` (how I look)
|
|
11
|
+
* and `useSelection` (which rows). Presentation can change; the visible
|
|
12
|
+
* field set stays.
|
|
13
|
+
*/
|
|
14
|
+
export type ColumnSpec = {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
/** When true, the column may leave the view. Required columns stay, and
|
|
18
|
+
* the submenu shows them checked and disabled so the split is visible. */
|
|
19
|
+
optional?: boolean;
|
|
20
|
+
/** Odoo `optional="hide"`: start off. Only for optional columns. */
|
|
21
|
+
defaultHidden?: boolean;
|
|
22
|
+
};
|
|
23
|
+
export type ColumnControl = {
|
|
24
|
+
visibleIds: readonly string[];
|
|
25
|
+
isVisible: (id: string) => boolean;
|
|
26
|
+
isRequired: (id: string) => boolean;
|
|
27
|
+
toggle: (id: string) => void;
|
|
28
|
+
optional: readonly ColumnSpec[];
|
|
29
|
+
required: readonly ColumnSpec[];
|
|
30
|
+
hasOptional: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare function isRequiredColumn(column: ColumnSpec): boolean;
|
|
33
|
+
export declare function defaultVisibleIds(columns: readonly ColumnSpec[]): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Required columns always stay. Unknown ids drop out. If everything optional
|
|
36
|
+
* was unchecked, the first column remains — a table with zero columns is not
|
|
37
|
+
* a table.
|
|
38
|
+
*/
|
|
39
|
+
export declare function reconcileVisible(columns: readonly ColumnSpec[], visibleIds: readonly string[]): string[];
|
|
40
|
+
export declare function toggleColumn(columns: readonly ColumnSpec[], visibleIds: readonly string[], id: string): string[];
|
|
41
|
+
export declare function visibleColumns<T extends {
|
|
42
|
+
id: string;
|
|
43
|
+
}>(columns: readonly T[], visibleIds: readonly string[]): T[];
|
|
44
|
+
export declare function useColumnControl(columns: readonly ColumnSpec[], options?: {
|
|
45
|
+
visibleIds?: readonly string[];
|
|
46
|
+
onVisibleIdsChange?: (ids: readonly string[]) => void;
|
|
47
|
+
}): ColumnControl;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
3
|
+
export function isRequiredColumn(column) {
|
|
4
|
+
return column.optional !== true;
|
|
5
|
+
}
|
|
6
|
+
export function defaultVisibleIds(columns) {
|
|
7
|
+
return columns
|
|
8
|
+
.filter((column) => isRequiredColumn(column) || column.defaultHidden !== true)
|
|
9
|
+
.map((column) => column.id);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Required columns always stay. Unknown ids drop out. If everything optional
|
|
13
|
+
* was unchecked, the first column remains — a table with zero columns is not
|
|
14
|
+
* a table.
|
|
15
|
+
*/
|
|
16
|
+
export function reconcileVisible(columns, visibleIds) {
|
|
17
|
+
if (columns.length === 0)
|
|
18
|
+
return [];
|
|
19
|
+
const known = new Set(columns.map((column) => column.id));
|
|
20
|
+
const visible = new Set(visibleIds.filter((id) => known.has(id)));
|
|
21
|
+
for (const column of columns) {
|
|
22
|
+
if (isRequiredColumn(column))
|
|
23
|
+
visible.add(column.id);
|
|
24
|
+
}
|
|
25
|
+
if (visible.size === 0)
|
|
26
|
+
visible.add(columns[0].id);
|
|
27
|
+
return columns.filter((column) => visible.has(column.id)).map((column) => column.id);
|
|
28
|
+
}
|
|
29
|
+
export function toggleColumn(columns, visibleIds, id) {
|
|
30
|
+
const column = columns.find((one) => one.id === id);
|
|
31
|
+
if (column === undefined || isRequiredColumn(column)) {
|
|
32
|
+
return reconcileVisible(columns, visibleIds);
|
|
33
|
+
}
|
|
34
|
+
const current = reconcileVisible(columns, visibleIds);
|
|
35
|
+
const next = current.includes(id) ? current.filter((one) => one !== id) : [...current, id];
|
|
36
|
+
return reconcileVisible(columns, next);
|
|
37
|
+
}
|
|
38
|
+
export function visibleColumns(columns, visibleIds) {
|
|
39
|
+
const wanted = new Set(visibleIds);
|
|
40
|
+
return columns.filter((column) => wanted.has(column.id));
|
|
41
|
+
}
|
|
42
|
+
export function useColumnControl(columns, options = {}) {
|
|
43
|
+
const signature = columns.map((column) => column.id).join("\0");
|
|
44
|
+
const [owned, setOwned] = useState(() => defaultVisibleIds(columns));
|
|
45
|
+
const [seen, setSeen] = useState(signature);
|
|
46
|
+
const controlled = options.onVisibleIdsChange !== undefined;
|
|
47
|
+
if (signature !== seen) {
|
|
48
|
+
setSeen(signature);
|
|
49
|
+
if (!controlled)
|
|
50
|
+
setOwned(defaultVisibleIds(columns));
|
|
51
|
+
}
|
|
52
|
+
const raw = controlled ? (options.visibleIds ?? defaultVisibleIds(columns)) : owned;
|
|
53
|
+
const visibleIds = reconcileVisible(columns, raw);
|
|
54
|
+
const setVisible = controlled ? options.onVisibleIdsChange : setOwned;
|
|
55
|
+
const toggle = useCallback((id) => {
|
|
56
|
+
setVisible(toggleColumn(columns, visibleIds, id));
|
|
57
|
+
}, [columns, visibleIds, setVisible]);
|
|
58
|
+
const optional = columns.filter((column) => column.optional === true);
|
|
59
|
+
const required = columns.filter((column) => isRequiredColumn(column));
|
|
60
|
+
return {
|
|
61
|
+
visibleIds,
|
|
62
|
+
isVisible: (id) => visibleIds.includes(id),
|
|
63
|
+
isRequired: (id) => {
|
|
64
|
+
const column = columns.find((one) => one.id === id);
|
|
65
|
+
return column === undefined ? true : isRequiredColumn(column);
|
|
66
|
+
},
|
|
67
|
+
toggle,
|
|
68
|
+
optional,
|
|
69
|
+
required,
|
|
70
|
+
hasOptional: optional.length > 0,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type GroupGrain, type GroupLevel } from "../../foundations/grouping.js";
|
|
2
|
+
/**
|
|
3
|
+
* LOS NIVELES, como estado que alguien edita.
|
|
4
|
+
*
|
|
5
|
+
* El vocabulario —qué es un nivel, qué es un montón, cómo se identifica— vive en
|
|
6
|
+
* `system/grouping.ts`, porque lo hablan las DOS capas: la lista arma el árbol y
|
|
7
|
+
* la tabla lo dibuja, y una primitiva no puede importar de un contexto. Acá
|
|
8
|
+
* queda sólo el CONTROL, que es de la lista como lo son el de columnas y el de
|
|
9
|
+
* plegado.
|
|
10
|
+
*/
|
|
11
|
+
/** Los niveles puestos, y las cuatro cosas que se les hacen. El orden ES la
|
|
12
|
+
* jerarquía: mover un nivel de lugar cambia por qué se parte primero. */
|
|
13
|
+
export type GroupingControl = {
|
|
14
|
+
levels: readonly GroupLevel[];
|
|
15
|
+
/**
|
|
16
|
+
* Pone un nivel. Un campo de fecha puede estar puesto con VARIOS granos —«por
|
|
17
|
+
* mes y adentro por día»— así que lo que no se duplica es el par campo+grano,
|
|
18
|
+
* no el campo. Agrupar dos veces por exactamente lo mismo parte en montones de
|
|
19
|
+
* uno, y eso siempre es un error de tipeo.
|
|
20
|
+
*/
|
|
21
|
+
add: (field: string, grain?: GroupGrain) => void;
|
|
22
|
+
remove: (field: string, grain?: GroupGrain) => void;
|
|
23
|
+
/** Tildar y destildar en un gesto, que es lo que hace una casilla. */
|
|
24
|
+
toggle: (field: string, grain?: GroupGrain) => void;
|
|
25
|
+
has: (field: string, grain?: GroupGrain) => boolean;
|
|
26
|
+
/** Mueve un nivel `delta` lugares. Se queda en los bordes en vez de dar la
|
|
27
|
+
* vuelta: llegar al primero y volver al último no es lo que nadie espera de
|
|
28
|
+
* una flecha «subir». */
|
|
29
|
+
move: (field: string, delta: number) => void;
|
|
30
|
+
clear: () => void;
|
|
31
|
+
};
|
|
32
|
+
export declare function useGroupingControl(options?: {
|
|
33
|
+
levels?: readonly GroupLevel[];
|
|
34
|
+
onLevelsChange?: (levels: readonly GroupLevel[]) => void;
|
|
35
|
+
}): GroupingControl;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { levelKey, moveLevel, normalizeLevels, } from "../../foundations/grouping.js";
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
// One empty array for every render: a fresh `[]` would change the callbacks below each time.
|
|
5
|
+
const NO_LEVELS = [];
|
|
6
|
+
export function useGroupingControl(options = {}) {
|
|
7
|
+
const [owned, setOwned] = useState(options.levels ?? NO_LEVELS);
|
|
8
|
+
const controlled = options.onLevelsChange !== undefined;
|
|
9
|
+
const levels = controlled ? (options.levels ?? NO_LEVELS) : owned;
|
|
10
|
+
const setLevels = controlled ? options.onLevelsChange : setOwned;
|
|
11
|
+
const keyOf = (field, grain) => levelKey(grain === undefined ? { field } : { field, grain });
|
|
12
|
+
const has = useCallback((field, grain) => levels.some((one) => levelKey(one) === keyOf(field, grain)), [levels]);
|
|
13
|
+
const add = useCallback((field, grain) => {
|
|
14
|
+
if (levels.some((one) => levelKey(one) === keyOf(field, grain)))
|
|
15
|
+
return;
|
|
16
|
+
/* Se normaliza SIEMPRE y no sólo al agregar fechas: es la única forma de
|
|
17
|
+
que tildar «día» y después «mes» dé «mes › día» sin que quien tilda
|
|
18
|
+
tenga que saber en qué orden hacerlo. */
|
|
19
|
+
setLevels(normalizeLevels([...levels, grain === undefined ? { field } : { field, grain }]));
|
|
20
|
+
}, [levels, setLevels]);
|
|
21
|
+
const remove = useCallback((field, grain) => {
|
|
22
|
+
setLevels(grain === undefined && !levels.some((one) => levelKey(one) === field)
|
|
23
|
+
? /* Sin grano y sin coincidencia exacta: se saca el campo ENTERO, con
|
|
24
|
+
todos sus granos. Es lo que quiere decir la × de un campo de
|
|
25
|
+
fecha que está puesto por mes y por día. */
|
|
26
|
+
levels.filter((one) => one.field !== field)
|
|
27
|
+
: levels.filter((one) => levelKey(one) !== keyOf(field, grain)));
|
|
28
|
+
}, [levels, setLevels]);
|
|
29
|
+
return {
|
|
30
|
+
levels,
|
|
31
|
+
has,
|
|
32
|
+
add,
|
|
33
|
+
remove,
|
|
34
|
+
toggle: useCallback((field, grain) => {
|
|
35
|
+
if (levels.some((one) => levelKey(one) === keyOf(field, grain)))
|
|
36
|
+
remove(field, grain);
|
|
37
|
+
else
|
|
38
|
+
add(field, grain);
|
|
39
|
+
}, [levels, add, remove]),
|
|
40
|
+
move: useCallback((field, delta) => {
|
|
41
|
+
setLevels(moveLevel(levels, field, delta));
|
|
42
|
+
}, [levels, setLevels]),
|
|
43
|
+
clear: useCallback(() => {
|
|
44
|
+
setLevels([]);
|
|
45
|
+
}, [setLevels]),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { type ColumnControl, type ColumnSpec, defaultVisibleIds, reconcileVisible, toggleColumn, useColumnControl, visibleColumns, } from "./columns.js";
|
|
2
|
+
export { ColumnsMenu, specsFromColumns, } from "./ColumnsMenu.js";
|
|
3
|
+
export { Listing } from "./Listing.js";
|
|
4
|
+
export { type ListIdentity, ListingBar } from "./ListingBar.js";
|
|
5
|
+
export { type ClaimedField, claimedFields, columnsFromModel, readModelValue, } from "./modelColumns.js";
|
|
6
|
+
export { type CursorSource, DEFAULT_PAGE_SIZE, PAGE_SIZES, type Pages, type PaginationBridge, type ServerPage, useCursorPagination, usePagination, } from "./pages.js";
|
|
7
|
+
export { Pagination } from "./Pagination.js";
|
|
8
|
+
export { RecordLink } from "./RecordLink.js";
|
|
9
|
+
export { GroupHeader, Row } from "./Row.js";
|
|
10
|
+
export { type Selection, useSelection } from "./selection.js";
|
|
11
|
+
export { type SetControl, useSetControl } from "./set.js";
|
|
12
|
+
export { toggleCollapsed, type TreeControl, useTreeControl, } from "./tree.js";
|
|
13
|
+
export { type ViewOption, ViewSwitcher, } from "./ViewSwitcher.js";
|
|
14
|
+
export { type GroupingControl, useGroupingControl, } from "./grouping.js";
|
|
15
|
+
export { GroupingLevels, type GroupingLevelsProps, } from "./GroupingLevels.js";
|
|
16
|
+
export { RecordCard } from "./RecordCard.js";
|
|
17
|
+
export { type SortField, SortMenu, type SortMenuProps, } from "./SortMenu.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* LISTING — the pieces a collection of N records is drawn with: rows, columns, paging,
|
|
2
|
+
sorting, grouping. The `List` view composes them; any other component may reuse them. */
|
|
3
|
+
export { defaultVisibleIds, reconcileVisible, toggleColumn, useColumnControl, visibleColumns, } from "./columns.js";
|
|
4
|
+
export { ColumnsMenu, specsFromColumns, } from "./ColumnsMenu.js";
|
|
5
|
+
export { Listing } from "./Listing.js";
|
|
6
|
+
export { ListingBar } from "./ListingBar.js";
|
|
7
|
+
export { claimedFields, columnsFromModel, readModelValue, } from "./modelColumns.js";
|
|
8
|
+
export { DEFAULT_PAGE_SIZE, PAGE_SIZES, useCursorPagination, usePagination, } from "./pages.js";
|
|
9
|
+
export { Pagination } from "./Pagination.js";
|
|
10
|
+
export { RecordLink } from "./RecordLink.js";
|
|
11
|
+
export { GroupHeader, Row } from "./Row.js";
|
|
12
|
+
export { useSelection } from "./selection.js";
|
|
13
|
+
export { useSetControl } from "./set.js";
|
|
14
|
+
export { toggleCollapsed, useTreeControl, } from "./tree.js";
|
|
15
|
+
export { ViewSwitcher, } from "./ViewSwitcher.js";
|
|
16
|
+
/* El control de los niveles. El vocabulario está en `system/grouping.ts`. */
|
|
17
|
+
export { useGroupingControl, } from "./grouping.js";
|
|
18
|
+
export { GroupingLevels, } from "./GroupingLevels.js";
|
|
19
|
+
export { RecordCard } from "./RecordCard.js";
|
|
20
|
+
export { SortMenu, } from "./SortMenu.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type ModelDefinition } from "../search/model.js";
|
|
2
|
+
import type { DataColumn } from "../../primitives/table/DataTable.js";
|
|
3
|
+
/** Qué campo del modelo muestra una columna declarada a mano.
|
|
4
|
+
*
|
|
5
|
+
* Sin esto, «filas» y `row_count` son dos cosas distintas y el campo
|
|
6
|
+
* aparecería dos veces: una con el rótulo bueno y otra cruda. */
|
|
7
|
+
export type ClaimedField = {
|
|
8
|
+
id: string;
|
|
9
|
+
field?: string;
|
|
10
|
+
};
|
|
11
|
+
/** Los campos que las columnas declaradas ya contestan.
|
|
12
|
+
*
|
|
13
|
+
* in [{id:"rows", field:"row_count"}, {id:"name"}]
|
|
14
|
+
* out Set {"row_count", "name"}
|
|
15
|
+
*
|
|
16
|
+
* El `id` cuenta como reclamo cuando coincide con el nombre del campo: la
|
|
17
|
+
* mayoría de las columnas se llaman como lo que muestran. */
|
|
18
|
+
export declare function claimedFields(columns: readonly ClaimedField[]): Set<string>;
|
|
19
|
+
/** Un valor del registro, en algo que una celda pueda ordenar y dibujar.
|
|
20
|
+
*
|
|
21
|
+
* in ["edad","sexo"] un `text[]`
|
|
22
|
+
* out "edad, sexo"
|
|
23
|
+
* in [] una lista vacía no es un texto vacío
|
|
24
|
+
* out null la tabla dibuja «—»
|
|
25
|
+
* in "2026-09-13T04:17:00"
|
|
26
|
+
* out "2026-09-13T04:17:00"
|
|
27
|
+
*
|
|
28
|
+
* Las fechas se dejan como vienen —ISO— porque en ISO el orden alfabético ES
|
|
29
|
+
* el cronológico, y así la columna ordena bien sin parsear nada. */
|
|
30
|
+
export declare function readModelValue(raw: unknown): string | number | boolean | null;
|
|
31
|
+
/**
|
|
32
|
+
* Las columnas declaradas, más una por cada campo del modelo que ninguna
|
|
33
|
+
* contestaba.
|
|
34
|
+
*
|
|
35
|
+
* in model el `model_meta_data` de `account.move` — 12 campos
|
|
36
|
+
* in declared 6 columnas escritas a mano en la vista
|
|
37
|
+
* out las 6, y detrás `journal_id`, `currency_id`, `invoice_origin`,
|
|
38
|
+
* `payment_reference`, `fiscal_position_id`, `company_id`,
|
|
39
|
+
* `move_type`, todas apagadas
|
|
40
|
+
*
|
|
41
|
+
* El rótulo de las agregadas sale de `model.translations` en el idioma pedido —el mismo
|
|
42
|
+
* `translations()` que el modelo publica para todo lo demás—, con el `default` que ese mismo
|
|
43
|
+
* modelo declaró si el idioma no está, y el nombre del campo tal cual sólo si el modelo no
|
|
44
|
+
* dijo ninguna palabra sobre él. Nunca puede desincronizarse con el motor porque sale de la
|
|
45
|
+
* MISMA respuesta que nombra el campo — a diferencia de un diccionario mantenido acá.
|
|
46
|
+
*
|
|
47
|
+
* `model === null` —todavía no llegó la definición— devuelve las declaradas sin
|
|
48
|
+
* tocar, que es exactamente lo que había antes.
|
|
49
|
+
*/
|
|
50
|
+
export declare function columnsFromModel<T extends object>(declared: readonly DataColumn<T>[], model: ModelDefinition | null | undefined, locale?: string): DataColumn<T>[];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { fieldLabel } from "../search/model.js";
|
|
3
|
+
/** Los campos que las columnas declaradas ya contestan.
|
|
4
|
+
*
|
|
5
|
+
* in [{id:"rows", field:"row_count"}, {id:"name"}]
|
|
6
|
+
* out Set {"row_count", "name"}
|
|
7
|
+
*
|
|
8
|
+
* El `id` cuenta como reclamo cuando coincide con el nombre del campo: la
|
|
9
|
+
* mayoría de las columnas se llaman como lo que muestran. */
|
|
10
|
+
export function claimedFields(columns) {
|
|
11
|
+
const claimed = new Set();
|
|
12
|
+
for (const column of columns) {
|
|
13
|
+
claimed.add(column.field ?? column.id);
|
|
14
|
+
if (column.field !== undefined)
|
|
15
|
+
claimed.add(column.id);
|
|
16
|
+
}
|
|
17
|
+
return claimed;
|
|
18
|
+
}
|
|
19
|
+
/** Un valor del registro, en algo que una celda pueda ordenar y dibujar.
|
|
20
|
+
*
|
|
21
|
+
* in ["edad","sexo"] un `text[]`
|
|
22
|
+
* out "edad, sexo"
|
|
23
|
+
* in [] una lista vacía no es un texto vacío
|
|
24
|
+
* out null la tabla dibuja «—»
|
|
25
|
+
* in "2026-09-13T04:17:00"
|
|
26
|
+
* out "2026-09-13T04:17:00"
|
|
27
|
+
*
|
|
28
|
+
* Las fechas se dejan como vienen —ISO— porque en ISO el orden alfabético ES
|
|
29
|
+
* el cronológico, y así la columna ordena bien sin parsear nada. */
|
|
30
|
+
export function readModelValue(raw) {
|
|
31
|
+
if (raw === null || raw === undefined)
|
|
32
|
+
return null;
|
|
33
|
+
if (Array.isArray(raw))
|
|
34
|
+
return raw.length === 0 ? null : raw.join(", ");
|
|
35
|
+
if (typeof raw === "number" || typeof raw === "boolean")
|
|
36
|
+
return raw;
|
|
37
|
+
if (typeof raw === "string")
|
|
38
|
+
return raw === "" ? null : raw;
|
|
39
|
+
return JSON.stringify(raw);
|
|
40
|
+
}
|
|
41
|
+
/** Cuánto ancho merece un campo, por su tipo. Una huella son 64 caracteres y
|
|
42
|
+
* una cuenta son seis: darles el mismo ancho arruina las dos. */
|
|
43
|
+
function widthFor(type) {
|
|
44
|
+
if (type === "integer" || type === "number")
|
|
45
|
+
return 120;
|
|
46
|
+
if (type === "datetime" || type === "date")
|
|
47
|
+
return 190;
|
|
48
|
+
if (type === "boolean")
|
|
49
|
+
return 90;
|
|
50
|
+
return 260;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Las columnas declaradas, más una por cada campo del modelo que ninguna
|
|
54
|
+
* contestaba.
|
|
55
|
+
*
|
|
56
|
+
* in model el `model_meta_data` de `account.move` — 12 campos
|
|
57
|
+
* in declared 6 columnas escritas a mano en la vista
|
|
58
|
+
* out las 6, y detrás `journal_id`, `currency_id`, `invoice_origin`,
|
|
59
|
+
* `payment_reference`, `fiscal_position_id`, `company_id`,
|
|
60
|
+
* `move_type`, todas apagadas
|
|
61
|
+
*
|
|
62
|
+
* El rótulo de las agregadas sale de `model.translations` en el idioma pedido —el mismo
|
|
63
|
+
* `translations()` que el modelo publica para todo lo demás—, con el `default` que ese mismo
|
|
64
|
+
* modelo declaró si el idioma no está, y el nombre del campo tal cual sólo si el modelo no
|
|
65
|
+
* dijo ninguna palabra sobre él. Nunca puede desincronizarse con el motor porque sale de la
|
|
66
|
+
* MISMA respuesta que nombra el campo — a diferencia de un diccionario mantenido acá.
|
|
67
|
+
*
|
|
68
|
+
* `model === null` —todavía no llegó la definición— devuelve las declaradas sin
|
|
69
|
+
* tocar, que es exactamente lo que había antes.
|
|
70
|
+
*/
|
|
71
|
+
export function columnsFromModel(declared, model, locale = "default") {
|
|
72
|
+
if (model === null || model === undefined)
|
|
73
|
+
return [...declared];
|
|
74
|
+
const claimed = claimedFields(declared);
|
|
75
|
+
const extra = [];
|
|
76
|
+
for (const [field, definition] of Object.entries(model.fields)) {
|
|
77
|
+
if (claimed.has(field))
|
|
78
|
+
continue;
|
|
79
|
+
const numeric = definition.type === "integer" || definition.type === "number";
|
|
80
|
+
extra.push({
|
|
81
|
+
id: field,
|
|
82
|
+
field,
|
|
83
|
+
header: fieldLabel(model, field, locale),
|
|
84
|
+
value: (row) => readModelValue(row[field]),
|
|
85
|
+
/* El valor entero en el `title`: una huella de 64 caracteres no entra en
|
|
86
|
+
ninguna celda, y cortada no sirve para nada — salvo para pasar el mouse
|
|
87
|
+
por encima y leerla. */
|
|
88
|
+
cell: (row) => {
|
|
89
|
+
const value = readModelValue(row[field]);
|
|
90
|
+
if (value === null)
|
|
91
|
+
return _jsx("span", { className: "text-subtle", children: "\u2014" });
|
|
92
|
+
const text = String(value);
|
|
93
|
+
return (_jsx("span", { className: "block truncate text-muted", title: text, children: text }));
|
|
94
|
+
},
|
|
95
|
+
...(numeric ? { numeric: true } : {}),
|
|
96
|
+
sortable: definition.sortable,
|
|
97
|
+
optional: true,
|
|
98
|
+
defaultHidden: true,
|
|
99
|
+
width: widthFor(definition.type),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return [...declared, ...extra];
|
|
103
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many records a list shows until someone asks for another size.
|
|
3
|
+
*
|
|
4
|
+
* 80 is the product default: enough to scan a catalog without paging every
|
|
5
|
+
* twenty-five rows, still small enough that a table stays a table. 25 / 80 / 100
|
|
6
|
+
* are the presets; any other positive integer is valid and is kept.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_PAGE_SIZE = 80;
|
|
9
|
+
export declare const PAGE_SIZES: readonly [25, 80, 100];
|
|
10
|
+
/** What `Pagination` needs, already computed from a set in memory. */
|
|
11
|
+
export interface PaginationBridge {
|
|
12
|
+
from: number;
|
|
13
|
+
to: number;
|
|
14
|
+
total: number;
|
|
15
|
+
pageSize: number;
|
|
16
|
+
onPageSizeChange: (next: number) => void;
|
|
17
|
+
onPrev?: () => void;
|
|
18
|
+
onNext?: () => void;
|
|
19
|
+
}
|
|
20
|
+
export interface Pages<Row> {
|
|
21
|
+
pageItems: Row[];
|
|
22
|
+
bridge: PaginationBridge;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Cut an in-memory set into pages.
|
|
26
|
+
*
|
|
27
|
+
* `pageSize` is controlled when `onPageSizeChange` is passed — that is how a
|
|
28
|
+
* view context keeps the size across filters. Without it the hook owns the
|
|
29
|
+
* size, defaulting to 80.
|
|
30
|
+
*
|
|
31
|
+
* The current page is clamped in the calculation, not in an effect: if a
|
|
32
|
+
* filter leaves fewer pages than the current one, the next read already
|
|
33
|
+
* lands on the last page that exists.
|
|
34
|
+
*/
|
|
35
|
+
export declare function usePagination<Row>(items: readonly Row[], options?: {
|
|
36
|
+
pageSize?: number;
|
|
37
|
+
onPageSizeChange?: (next: number) => void;
|
|
38
|
+
}): Pages<Row>;
|
|
39
|
+
/** What a server answered about a page: the records, where the next one starts, how many
|
|
40
|
+
* match in total. `exact: false` on the count means *at least* that many — a server that
|
|
41
|
+
* stops counting at a ceiling so its cost does not grow with the table. */
|
|
42
|
+
export interface ServerPage<Row> {
|
|
43
|
+
items: readonly Row[];
|
|
44
|
+
cursor: string | null;
|
|
45
|
+
count: {
|
|
46
|
+
value: number;
|
|
47
|
+
exact: boolean;
|
|
48
|
+
} | null;
|
|
49
|
+
}
|
|
50
|
+
/** What a cursor-paged list needs from whoever fetches it. */
|
|
51
|
+
export interface CursorSource<Row> {
|
|
52
|
+
/** The page at this cursor. `undefined` asks for the first one. */
|
|
53
|
+
fetch: (cursor: string | undefined, pageSize: number) => Promise<ServerPage<Row>>;
|
|
54
|
+
/** Anything that changes WHICH records are being paged — a filter, an ordering. Pagination
|
|
55
|
+
* starts over when this changes, because page 3 of another reading is not page 3. */
|
|
56
|
+
resetKey?: unknown;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Cut a *server-side* set into pages, by cursor.
|
|
60
|
+
*
|
|
61
|
+
* `usePagination` slices an array that is already in hand; this one never holds the whole set.
|
|
62
|
+
* Same `bridge`, so `Pagination` draws it identically — the difference is invisible to a view.
|
|
63
|
+
*
|
|
64
|
+
* **Going back is a walk, not a jump.** A keyset cursor names the last row seen, so it points
|
|
65
|
+
* forward and nothing points back. Rather than fake it with an offset — which would reintroduce
|
|
66
|
+
* exactly the incoherence the cursor exists to avoid — the cursors already walked are kept, and
|
|
67
|
+
* `onPrev` re-fetches with the one before. That is one request, not a re-walk from the start.
|
|
68
|
+
*
|
|
69
|
+
* **Changing the size starts over**, and that is not a limitation: a cursor belongs to the page
|
|
70
|
+
* size it was minted under, so keeping it would land somewhere nobody asked for.
|
|
71
|
+
*/
|
|
72
|
+
export declare function useCursorPagination<Row>(source: CursorSource<Row>, options?: {
|
|
73
|
+
pageSize?: number;
|
|
74
|
+
onPageSizeChange?: (next: number) => void;
|
|
75
|
+
}): Pages<Row> & {
|
|
76
|
+
loading: boolean;
|
|
77
|
+
error: unknown;
|
|
78
|
+
};
|