@sincpro/web-ui 0.1.0 → 1.0.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/README.md +40 -13
- package/dist/components/activity/Activity.d.ts +69 -0
- package/dist/components/activity/Activity.js +279 -0
- package/dist/components/activity/ActivityBadge.d.ts +16 -0
- package/dist/components/activity/ActivityBadge.js +29 -0
- package/dist/components/{chatter → activity}/Messages.d.ts +2 -2
- package/dist/components/{chatter → activity}/Messages.js +9 -6
- package/dist/components/activity/index.d.ts +4 -0
- package/dist/components/activity/index.js +5 -0
- package/dist/components/assist/AssistMark.d.ts +3 -3
- package/dist/components/assist/AssistMark.js +1 -1
- package/dist/components/assist/assist.d.ts +1 -1
- package/dist/components/assist/assist.js +1 -1
- package/dist/components/calendar/RecordCalendar.d.ts +48 -0
- package/dist/components/calendar/RecordCalendar.js +101 -0
- package/dist/components/calendar/index.d.ts +1 -0
- package/dist/components/calendar/index.js +1 -0
- package/dist/components/form/Sheet.d.ts +4 -0
- package/dist/components/form/Sheet.js +16 -5
- package/dist/components/gantt/RecordGantt.d.ts +44 -0
- package/dist/components/gantt/RecordGantt.js +110 -0
- package/dist/components/gantt/index.d.ts +1 -0
- package/dist/components/gantt/index.js +1 -0
- package/dist/components/index.d.ts +4 -1
- package/dist/components/index.js +4 -1
- package/dist/components/lines/LineItems.d.ts +45 -5
- package/dist/components/lines/LineItems.js +106 -18
- package/dist/components/listing/BoardColumns.d.ts +39 -0
- package/dist/components/listing/BoardColumns.js +138 -0
- package/dist/components/listing/Listing.js +1 -1
- package/dist/components/listing/ListingBar.d.ts +46 -2
- package/dist/components/listing/ListingBar.js +47 -5
- package/dist/components/listing/PivotExplorer.d.ts +34 -0
- package/dist/components/listing/PivotExplorer.js +86 -0
- package/dist/components/listing/RecordCard.d.ts +6 -8
- package/dist/components/listing/RecordCard.js +13 -7
- package/dist/components/listing/Row.d.ts +25 -2
- package/dist/components/listing/Row.js +30 -13
- package/dist/components/listing/index.d.ts +2 -0
- package/dist/components/listing/index.js +2 -0
- package/dist/components/listing/set.d.ts +1 -1
- package/dist/components/navigation/index.d.ts +0 -2
- package/dist/components/navigation/index.js +2 -3
- package/dist/components/record/RecordAudit.d.ts +17 -0
- package/dist/components/record/RecordAudit.js +27 -0
- package/dist/components/record/RecordHeader.d.ts +27 -17
- package/dist/components/record/RecordHeader.js +64 -20
- package/dist/components/record/RecordNotices.d.ts +37 -0
- package/dist/components/record/RecordNotices.js +75 -0
- package/dist/components/record/RecordRibbon.d.ts +13 -0
- package/dist/components/record/RecordRibbon.js +22 -0
- package/dist/components/record/ThumbBar.js +1 -1
- package/dist/components/record/index.d.ts +2 -0
- package/dist/components/record/index.js +2 -0
- package/dist/components/search/CriteriaChips.d.ts +6 -1
- package/dist/components/search/CriteriaChips.js +71 -20
- package/dist/components/search/SearchBar.d.ts +4 -1
- package/dist/components/search/SearchBar.js +15 -7
- package/dist/components/timeline/RecordTimeline.d.ts +60 -0
- package/dist/components/timeline/RecordTimeline.js +214 -0
- package/dist/components/timeline/TimelineToolbar.d.ts +30 -0
- package/dist/components/timeline/TimelineToolbar.js +31 -0
- package/dist/components/timeline/index.d.ts +3 -0
- package/dist/components/timeline/index.js +3 -0
- package/dist/components/timeline/time.d.ts +43 -0
- package/dist/components/timeline/time.js +135 -0
- package/dist/foundations/copy.d.ts +194 -18
- package/dist/foundations/copy.es.js +142 -15
- package/dist/foundations/copy.js +133 -11
- package/dist/foundations/index.d.ts +1 -1
- package/dist/foundations/match.d.ts +23 -0
- package/dist/foundations/match.js +63 -0
- package/dist/foundations/slots.d.ts +1 -1
- package/dist/foundations/slots.js +8 -3
- package/dist/foundations/types.d.ts +7 -0
- package/dist/foundations/types.js +0 -36
- package/dist/primitives/block/index.d.ts +1 -1
- package/dist/primitives/block/index.js +1 -1
- package/dist/primitives/cell/CellStack.d.ts +7 -1
- package/dist/primitives/cell/CellStack.js +5 -2
- package/dist/primitives/choice/ComboBox.d.ts +3 -0
- package/dist/primitives/choice/ComboBox.js +10 -6
- package/dist/primitives/choice/KanbanState.d.ts +14 -0
- package/dist/primitives/choice/KanbanState.js +37 -0
- package/dist/primitives/choice/Priority.d.ts +20 -0
- package/dist/primitives/choice/Priority.js +41 -0
- package/dist/primitives/choice/SearchField.d.ts +10 -1
- package/dist/primitives/choice/SearchField.js +21 -2
- package/dist/primitives/choice/Slider.d.ts +6 -1
- package/dist/primitives/choice/Slider.js +2 -2
- package/dist/primitives/choice/index.d.ts +2 -0
- package/dist/primitives/choice/index.js +2 -0
- package/dist/primitives/date/Calendar.js +30 -6
- package/dist/primitives/date/DatePicker.d.ts +11 -3
- package/dist/primitives/date/DatePicker.js +12 -8
- package/dist/primitives/date/DateTime.d.ts +35 -0
- package/dist/primitives/date/DateTime.js +100 -0
- package/dist/primitives/date/index.d.ts +1 -0
- package/dist/primitives/date/index.js +4 -2
- package/dist/primitives/dialog/Dialog.d.ts +9 -3
- package/dist/primitives/dialog/Dialog.js +41 -5
- package/dist/primitives/field/DropZone.js +1 -1
- package/dist/primitives/field/Field.d.ts +14 -2
- package/dist/primitives/field/Field.js +25 -14
- package/dist/primitives/field/copy.d.ts +16 -0
- package/dist/primitives/field/copy.js +69 -0
- package/dist/primitives/field/index.d.ts +2 -0
- package/dist/primitives/field/index.js +4 -0
- package/dist/primitives/field/note.d.ts +55 -0
- package/dist/primitives/field/note.js +117 -0
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/menu/Menu.d.ts +28 -0
- package/dist/primitives/menu/Menu.js +47 -17
- package/dist/primitives/notice/Callout.js +1 -1
- package/dist/primitives/notice/Flag.d.ts +13 -0
- package/dist/primitives/notice/Flag.js +31 -0
- package/dist/primitives/notice/index.d.ts +2 -1
- package/dist/primitives/notice/index.js +2 -1
- package/dist/primitives/overlay/Preview.d.ts +19 -0
- package/dist/primitives/overlay/Preview.js +69 -0
- package/dist/primitives/overlay/index.d.ts +1 -0
- package/dist/primitives/overlay/index.js +3 -1
- package/dist/primitives/picker/MatchedText.d.ts +10 -0
- package/dist/primitives/picker/MatchedText.js +12 -0
- package/dist/primitives/picker/Picker.d.ts +67 -4
- package/dist/primitives/picker/Picker.js +142 -23
- package/dist/primitives/table/CellCopy.d.ts +20 -0
- package/dist/primitives/table/CellCopy.js +23 -0
- package/dist/primitives/table/DataTable.d.ts +55 -1
- package/dist/primitives/table/DataTable.js +126 -40
- package/dist/primitives/table/EditableTable.d.ts +102 -2
- package/dist/primitives/table/EditableTable.js +287 -32
- package/dist/primitives/table/ReportTable.js +2 -1
- package/dist/primitives/table/RowActions.js +7 -2
- package/dist/primitives/table/SheetTable.d.ts +4 -1
- package/dist/primitives/table/SheetTable.js +38 -5
- package/dist/primitives/table/Table.d.ts +5 -0
- package/dist/primitives/table/Table.js +17 -8
- package/dist/primitives/table/index.d.ts +2 -2
- package/dist/primitives/table/index.js +1 -1
- package/dist/primitives/tabs/Tabs.d.ts +2 -2
- package/dist/primitives/tabs/Tabs.js +1 -1
- package/dist/primitives/tag/Tags.d.ts +2 -0
- package/dist/primitives/tag/Tags.js +2 -1
- package/dist/primitives/tag/TagsField.d.ts +13 -0
- package/dist/primitives/tag/TagsField.js +39 -10
- package/dist/primitives/tag/choices.d.ts +2 -0
- package/dist/primitives/tree/Tree.d.ts +56 -0
- package/dist/primitives/tree/Tree.js +74 -0
- package/dist/primitives/tree/index.d.ts +1 -0
- package/dist/primitives/tree/index.js +3 -0
- package/dist/primitives/value/Avatar.d.ts +16 -0
- package/dist/primitives/value/Avatar.js +91 -0
- package/dist/primitives/value/Copyable.d.ts +11 -0
- package/dist/primitives/value/Copyable.js +27 -6
- package/dist/primitives/value/Fecha.js +10 -1
- package/dist/primitives/value/Person.d.ts +6 -51
- package/dist/primitives/value/Person.js +9 -148
- package/dist/primitives/value/ProgressBar.d.ts +31 -0
- package/dist/primitives/value/ProgressBar.js +53 -0
- package/dist/primitives/value/Relation.d.ts +22 -5
- package/dist/primitives/value/Relation.js +42 -14
- package/dist/primitives/value/Totals.d.ts +102 -7
- package/dist/primitives/value/Totals.js +52 -2
- package/dist/primitives/value/Value.d.ts +0 -37
- package/dist/primitives/value/Value.js +0 -39
- package/dist/primitives/value/contact.d.ts +30 -0
- package/dist/primitives/value/contact.js +51 -0
- package/dist/primitives/value/index.d.ts +5 -4
- package/dist/primitives/value/index.js +5 -4
- package/dist/tokens.css +179 -7
- package/dist/views/list/List.d.ts +32 -3
- package/dist/views/list/List.js +51 -7
- package/dist/views/list/ListView.d.ts +1 -1
- package/dist/views/list/ListView.js +70 -13
- package/dist/views/list/columns.js +8 -1
- package/dist/views/list/derive.d.ts +12 -1
- package/dist/views/list/derive.js +29 -3
- package/dist/views/list/presentations.d.ts +5 -2
- package/dist/views/list/presentations.js +35 -3
- package/dist/views/list/types.d.ts +57 -1
- package/dist/views/list/useNarrowList.d.ts +10 -0
- package/dist/views/list/useNarrowList.js +20 -6
- package/dist/views/record/Record.d.ts +41 -8
- package/dist/views/record/Record.js +113 -28
- package/dist/views/record/index.d.ts +1 -1
- package/package.json +15 -4
- package/dist/components/chatter/Chatter.d.ts +0 -161
- package/dist/components/chatter/Chatter.js +0 -232
- package/dist/components/chatter/index.d.ts +0 -3
- package/dist/components/chatter/index.js +0 -4
- package/dist/components/navigation/CommandPalette.d.ts +0 -40
- package/dist/components/navigation/CommandPalette.js +0 -114
- package/dist/components/navigation/commands.d.ts +0 -30
- package/dist/components/navigation/commands.js +0 -30
- /package/dist/components/{chatter → activity}/Timeline.d.ts +0 -0
- /package/dist/components/{chatter → activity}/Timeline.js +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Tone } from "../../foundations/types.js";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
export type CalendarView = "month" | "week" | "day" | "agenda";
|
|
4
|
+
export type RecordCalendarProps<T> = {
|
|
5
|
+
items: readonly T[];
|
|
6
|
+
keyOf: (item: T) => string;
|
|
7
|
+
titleOf: (item: T) => string;
|
|
8
|
+
startOf: (item: T) => Date;
|
|
9
|
+
/** Without it the event lasts an hour, or the day when it is all-day. */
|
|
10
|
+
endOf?: (item: T) => Date | undefined;
|
|
11
|
+
allDayOf?: (item: T) => boolean;
|
|
12
|
+
/** What the event means: an overdue call `bad`, a confirmed meeting `good`. */
|
|
13
|
+
toneOf?: (item: T) => Tone;
|
|
14
|
+
/** Who it is with or whose it is — a face on the event, the photo or the initials. */
|
|
15
|
+
avatarOf?: (item: T) => {
|
|
16
|
+
name: string;
|
|
17
|
+
src?: string;
|
|
18
|
+
} | undefined;
|
|
19
|
+
/** A second line under the title: the customer, the place. */
|
|
20
|
+
metaOf?: (item: T) => string | undefined;
|
|
21
|
+
/** The whole event drawn by the app — the calendar's equivalent of a board's `card`. Gets
|
|
22
|
+
* the record and the time text the calendar computed («10:00 – 11:30»). */
|
|
23
|
+
renderEvent?: (item: T, time: string) => ReactNode;
|
|
24
|
+
/** The view it opens on. The calendar's own buttons switch it afterwards. */
|
|
25
|
+
view?: CalendarView;
|
|
26
|
+
/** The day it opens on. Today by default. */
|
|
27
|
+
date?: Date;
|
|
28
|
+
/** An event was dragged to another moment or stretched. The calendar does NOT keep the move:
|
|
29
|
+
* `items` is the truth — the app saves and passes the new dates back. */
|
|
30
|
+
onMove?: (item: T, start: Date, end: Date | null) => void;
|
|
31
|
+
/** A stretch of empty time was selected: create something there. */
|
|
32
|
+
onCreate?: (start: Date, end: Date, allDay: boolean) => void;
|
|
33
|
+
/** An event was pressed. */
|
|
34
|
+
onOpen?: (item: T) => void;
|
|
35
|
+
/** A height of its own: `auto` grows with the month, a number or `40rem` scrolls inside. */
|
|
36
|
+
height?: number | string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Records on a calendar — Odoo's calendar view: meetings, deliveries, due dates.
|
|
40
|
+
*
|
|
41
|
+
* Context: drawn by FullCalendar v7 (MIT standard views: month, week, day, agenda), dressed by
|
|
42
|
+
* `tokens.css` (its `forma` palette points at web-ui's tokens, so it follows the theme). It is a
|
|
43
|
+
* CONTROLLED view of the records: dragging or stretching an event reports `onMove` and the
|
|
44
|
+
* library's own change is undone at once — the app persists and passes the new `items`, so the
|
|
45
|
+
* screen never shows a date the backend refused. Resource rows (per person, per room) are
|
|
46
|
+
* FullCalendar's premium scheduler: a licence away, not built here.
|
|
47
|
+
*/
|
|
48
|
+
export declare function RecordCalendar<T>({ items, keyOf, titleOf, startOf, endOf, allDayOf, toneOf, avatarOf, metaOf, renderEvent, view, date, onMove, onCreate, onOpen, height, }: RecordCalendarProps<T>): import("react").JSX.Element;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import FullCalendar from "@fullcalendar/react";
|
|
4
|
+
import dayGridPlugin from "@fullcalendar/react/daygrid";
|
|
5
|
+
import interactionPlugin from "@fullcalendar/react/interaction";
|
|
6
|
+
import listPlugin from "@fullcalendar/react/list";
|
|
7
|
+
import esLocale from "@fullcalendar/react/locales/es";
|
|
8
|
+
import formaThemePlugin from "@fullcalendar/react/themes/forma";
|
|
9
|
+
import timeGridPlugin from "@fullcalendar/react/timegrid";
|
|
10
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
11
|
+
import { Avatar } from "../../primitives/value/Person.js";
|
|
12
|
+
const VIEW = {
|
|
13
|
+
month: "dayGridMonth",
|
|
14
|
+
week: "timeGridWeek",
|
|
15
|
+
day: "timeGridDay",
|
|
16
|
+
agenda: "listWeek",
|
|
17
|
+
};
|
|
18
|
+
/* An event takes the colour of what it MEANS — the five tones — and the surface colour on top,
|
|
19
|
+
which keeps ≥ 5.7:1 on every tone in both themes (see `tokens.css`). */
|
|
20
|
+
const TONE_COLOR = {
|
|
21
|
+
neutral: "var(--color-muted)",
|
|
22
|
+
info: "var(--color-info)",
|
|
23
|
+
good: "var(--color-good)",
|
|
24
|
+
warn: "var(--color-warn)",
|
|
25
|
+
bad: "var(--color-bad)",
|
|
26
|
+
accent: "var(--color-primary)",
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Records on a calendar — Odoo's calendar view: meetings, deliveries, due dates.
|
|
30
|
+
*
|
|
31
|
+
* Context: drawn by FullCalendar v7 (MIT standard views: month, week, day, agenda), dressed by
|
|
32
|
+
* `tokens.css` (its `forma` palette points at web-ui's tokens, so it follows the theme). It is a
|
|
33
|
+
* CONTROLLED view of the records: dragging or stretching an event reports `onMove` and the
|
|
34
|
+
* library's own change is undone at once — the app persists and passes the new `items`, so the
|
|
35
|
+
* screen never shows a date the backend refused. Resource rows (per person, per room) are
|
|
36
|
+
* FullCalendar's premium scheduler: a licence away, not built here.
|
|
37
|
+
*/
|
|
38
|
+
export function RecordCalendar({ items, keyOf, titleOf, startOf, endOf, allDayOf, toneOf, avatarOf, metaOf, renderEvent, view = "month", date, onMove, onCreate, onOpen, height = "auto", }) {
|
|
39
|
+
const copy = useUiCopy();
|
|
40
|
+
const byId = new Map(items.map((item) => [keyOf(item), item]));
|
|
41
|
+
const events = items.map((item) => {
|
|
42
|
+
const tone = toneOf?.(item);
|
|
43
|
+
const end = endOf?.(item);
|
|
44
|
+
return {
|
|
45
|
+
id: keyOf(item),
|
|
46
|
+
title: titleOf(item),
|
|
47
|
+
start: startOf(item),
|
|
48
|
+
...(end === undefined ? {} : { end }),
|
|
49
|
+
...(allDayOf === undefined ? {} : { allDay: allDayOf(item) }),
|
|
50
|
+
...(tone === undefined
|
|
51
|
+
? {}
|
|
52
|
+
: { color: TONE_COLOR[tone], textColor: "var(--color-surface)" }),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
/* A record on the calendar, not a bare title: its face, its time, its name and a second
|
|
56
|
+
line, in that reading order. Only when the app gave something more than a title. */
|
|
57
|
+
const rich = avatarOf !== undefined || metaOf !== undefined || renderEvent !== undefined;
|
|
58
|
+
const content = (info) => {
|
|
59
|
+
const item = byId.get(info.event.id);
|
|
60
|
+
if (item === undefined)
|
|
61
|
+
return null;
|
|
62
|
+
if (renderEvent !== undefined)
|
|
63
|
+
return renderEvent(item, info.timeText);
|
|
64
|
+
const person = avatarOf?.(item);
|
|
65
|
+
const meta = metaOf?.(item);
|
|
66
|
+
return (_jsxs("span", { className: "flex min-w-0 items-start gap-1.5 px-0.5 py-px", children: [person === undefined ? null : (_jsx(Avatar, { name: person.name, size: "sm", ...(person.src === undefined ? {} : { src: person.src }) })), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsxs("span", { className: "min-w-0 truncate", children: [info.timeText === "" ? null : (_jsxs("b", { className: "font-semibold tabular-nums", children: [info.timeText, " "] })), titleOf(item)] }), meta === undefined ? null : (_jsx("span", { className: "min-w-0 truncate opacity-80", children: meta }))] })] }));
|
|
67
|
+
};
|
|
68
|
+
const moved = (info) => {
|
|
69
|
+
const item = byId.get(info.event.id);
|
|
70
|
+
/* Undone first: the move is a request, and `items` answers it. */
|
|
71
|
+
info.revert();
|
|
72
|
+
if (item === undefined || info.event.start === null)
|
|
73
|
+
return;
|
|
74
|
+
onMove?.(item, info.event.start, info.event.end);
|
|
75
|
+
};
|
|
76
|
+
return (_jsx("div", { className: "min-w-0 text-meta", children: _jsx(FullCalendar, { plugins: [
|
|
77
|
+
dayGridPlugin,
|
|
78
|
+
timeGridPlugin,
|
|
79
|
+
listPlugin,
|
|
80
|
+
interactionPlugin,
|
|
81
|
+
formaThemePlugin,
|
|
82
|
+
], locales: [esLocale], locale: copy.locale.toLowerCase().startsWith("es") ? "es" : "en", initialView: VIEW[view], ...(date === undefined ? {} : { initialDate: date }), headerToolbar: {
|
|
83
|
+
start: "prev,next today",
|
|
84
|
+
center: "title",
|
|
85
|
+
end: "dayGridMonth,timeGridWeek,timeGridDay,listWeek",
|
|
86
|
+
}, height: height, events: events, editable: onMove !== undefined, selectable: onCreate !== undefined, nowIndicator: true,
|
|
87
|
+
/* The week and the day open on the working day, not on midnight. */
|
|
88
|
+
scrollTime: "07:00:00", ...(rich ? { eventContent: content } : {}), ...(onMove === undefined ? {} : { eventDrop: moved, eventResize: moved }), ...(onCreate === undefined
|
|
89
|
+
? {}
|
|
90
|
+
: {
|
|
91
|
+
select: (info) => onCreate(info.start, info.end, info.allDay),
|
|
92
|
+
}), ...(onOpen === undefined
|
|
93
|
+
? {}
|
|
94
|
+
: {
|
|
95
|
+
eventClick: (info) => {
|
|
96
|
+
const item = byId.get(info.event.id);
|
|
97
|
+
if (item !== undefined)
|
|
98
|
+
onOpen(item);
|
|
99
|
+
},
|
|
100
|
+
}) }) }));
|
|
101
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type CalendarView, RecordCalendar, type RecordCalendarProps, } from "./RecordCalendar.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RecordCalendar, } from "./RecordCalendar.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type SheetLayout } from "../../primitives/field/layout.js";
|
|
2
|
+
import { type FieldNote } from "../../primitives/field/note.js";
|
|
2
3
|
import type { LucideIcon } from "lucide-react";
|
|
3
4
|
import { type ReactNode } from "react";
|
|
4
5
|
export type { SheetLayout };
|
|
@@ -8,6 +9,9 @@ export type SheetField = {
|
|
|
8
9
|
/** Help on the label, as a `?`. Hover or click. */
|
|
9
10
|
hint?: string;
|
|
10
11
|
mono?: boolean;
|
|
12
|
+
/** A note that belongs to this fact — the same as `Field`'s: a flag by the label, and an
|
|
13
|
+
* entry in the record's activity. */
|
|
14
|
+
note?: FieldNote;
|
|
11
15
|
};
|
|
12
16
|
export declare function Sheet({ fields, columns, layout, head, children, }: {
|
|
13
17
|
fields?: readonly SheetField[];
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
4
4
|
import { FieldLayoutProvider, } from "../../primitives/field/layout.js";
|
|
5
|
+
import { NoteCard, NoteFlag } from "../../primitives/field/note.js";
|
|
5
6
|
import { Hint } from "../../primitives/hint/Hint.js";
|
|
6
|
-
import { Children } from "react";
|
|
7
|
+
import { Children, useId } from "react";
|
|
7
8
|
function Facts({ fields, columns, layout, }) {
|
|
8
9
|
const d = useDensity();
|
|
10
|
+
const idBase = useId();
|
|
9
11
|
const horizontal = layout === "horizontal";
|
|
10
12
|
/* Horizontal facts are a run of pairs, not a grid stretched across the
|
|
11
13
|
paper. A two-column grid put «Total Bs 12.480» on the left and «Vence
|
|
@@ -20,10 +22,15 @@ function Facts({ fields, columns, layout, }) {
|
|
|
20
22
|
? "flex min-w-0 flex-wrap items-baseline gap-x-6 gap-y-1.5"
|
|
21
23
|
: asGrid
|
|
22
24
|
? `@container grid min-w-0 ${grid} ${byDensity(d, "gap-x-8 gap-y-2.5", "gap-x-6 gap-y-2")}`
|
|
23
|
-
: `flex min-w-0 flex-col ${byDensity(d, "gap-2.5", "gap-2")}`, children: fields.map((field) => {
|
|
25
|
+
: `flex min-w-0 flex-col ${byDensity(d, "gap-2.5", "gap-2")}`, children: fields.map((field, at) => {
|
|
26
|
+
const noteId = field.note === undefined ? undefined : `${idBase}-${at}-note`;
|
|
24
27
|
const empty = field.value === undefined || field.value === null || field.value === "";
|
|
25
|
-
const
|
|
26
|
-
|
|
28
|
+
const card = field.note === undefined || noteId === undefined ? null : (_jsx(NoteCard, { note: field.note, id: noteId, owner: field.label }));
|
|
29
|
+
/* The note's card goes INSIDE the value: a `<dl>` holds only its terms and values.
|
|
30
|
+
With a note the value is a flex box, so the absolute card keeps the value's top
|
|
31
|
+
as its static position while being read after it. */
|
|
32
|
+
const value = (_jsxs("dd", { ...(noteId === undefined ? {} : { "aria-describedby": noteId }), className: `min-w-0 text-meta ${card === null ? "" : "flex"} ${empty ? "text-muted italic" : "text-ink"} ${field.mono === true ? "font-mono tabular-nums" : ""}`, children: [empty ? "—" : field.value, card] }));
|
|
33
|
+
const name = (_jsxs("dt", { className: `flex items-center gap-1 text-micro text-muted ${horizontal ? "shrink-0" : "min-w-0"}`, children: [_jsx("span", { children: field.label }), field.hint === undefined ? null : _jsx(Hint, { children: field.hint }), field.note === undefined ? null : _jsx(NoteFlag, { note: field.note })] }));
|
|
27
34
|
if (horizontal) {
|
|
28
35
|
/* A header fact is a pair: the number sits next to its name. The
|
|
29
36
|
8rem label column is for a form, where twenty values have to
|
|
@@ -79,5 +86,9 @@ export function SheetGroups({ children, columns = 2, }) {
|
|
|
79
86
|
export function SheetGroup({ title, hint, icon: Icon, children, }) {
|
|
80
87
|
const d = useDensity();
|
|
81
88
|
const withHeader = title !== undefined;
|
|
82
|
-
return (_jsxs("section", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-2.5", "gap-2")}`, children: [withHeader ? (
|
|
89
|
+
return (_jsxs("section", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-2.5", "gap-2")}`, children: [withHeader ? (
|
|
90
|
+
/* A group title is ABOVE its labels, not one of them: at the labels' size and weight
|
|
91
|
+
«Cliente» the group read like «Cliente» the field. One step up in size and weight,
|
|
92
|
+
and a hairline that says where the group starts. */
|
|
93
|
+
_jsxs("header", { className: "flex min-w-0 items-center gap-2 border-b border-line pb-1.5", children: [Icon === undefined ? null : (_jsx(Icon, { className: "size-(--size-icon-sm) shrink-0 text-muted", strokeWidth: 1.75, "aria-hidden": true })), _jsx("h3", { className: "min-w-0 truncate text-body font-semibold text-ink", children: title }), hint === undefined ? null : _jsx(Hint, { children: hint })] })) : hint === undefined ? null : (_jsx(Hint, { children: hint })), _jsx("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-2.5", "gap-2")}`, children: children })] }));
|
|
83
94
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** A dependency between two records: «B starts when A ends» (`e2s`, the usual one). */
|
|
2
|
+
export type GanttLink = {
|
|
3
|
+
id: string;
|
|
4
|
+
source: string;
|
|
5
|
+
target: string;
|
|
6
|
+
type?: "e2s" | "s2s" | "e2e" | "s2e";
|
|
7
|
+
};
|
|
8
|
+
export type GanttZoom = "day" | "week" | "month";
|
|
9
|
+
export type RecordGanttProps<T> = {
|
|
10
|
+
items: readonly T[];
|
|
11
|
+
keyOf: (item: T) => string;
|
|
12
|
+
titleOf: (item: T) => string;
|
|
13
|
+
startOf: (item: T) => Date;
|
|
14
|
+
endOf: (item: T) => Date;
|
|
15
|
+
/** 0 to 100. Without it the bar has no progress handle. */
|
|
16
|
+
progressOf?: (item: T) => number;
|
|
17
|
+
/** The record it hangs under — a task under its project. Summaries are drawn for parents. */
|
|
18
|
+
parentOf?: (item: T) => string | undefined;
|
|
19
|
+
/** A point in time rather than a span: a delivery, a sign-off. */
|
|
20
|
+
milestoneOf?: (item: T) => boolean;
|
|
21
|
+
links?: readonly GanttLink[];
|
|
22
|
+
/** The scale it opens on; the toolbar switches it. */
|
|
23
|
+
zoom?: GanttZoom;
|
|
24
|
+
/** A bar was dragged or stretched. The app persists and passes the new `items`. */
|
|
25
|
+
onMove?: (item: T, start: Date, end: Date) => void;
|
|
26
|
+
onProgress?: (item: T, progress: number) => void;
|
|
27
|
+
/** A bar was chosen. */
|
|
28
|
+
onOpen?: (item: T) => void;
|
|
29
|
+
/** The chart's height, toolbar included: it scrolls inside. */
|
|
30
|
+
height?: string;
|
|
31
|
+
/** The left grid's column title for the names. */
|
|
32
|
+
nameHeader?: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Records on a timeline — Odoo's Gantt view: projects, tasks, manufacturing orders.
|
|
36
|
+
*
|
|
37
|
+
* Context: drawn by SVAR React Gantt (MIT core: move, resize, progress, dependencies, zoom),
|
|
38
|
+
* dressed by `tokens.css` (its Willow theme's `--wx-*` variables point at web-ui's tokens).
|
|
39
|
+
* The chart keeps its own state while dragging; every finished change is reported (`onMove`,
|
|
40
|
+
* `onProgress`) and the app passes the saved `items` back, which the chart re-reads. Without
|
|
41
|
+
* `onMove` / `onProgress` it is read-only. Auto-scheduling, critical path and resource load are
|
|
42
|
+
* SVAR's PRO edition: a licence away, not built here.
|
|
43
|
+
*/
|
|
44
|
+
export declare function RecordGantt<T>({ items, keyOf, titleOf, startOf, endOf, progressOf, parentOf, milestoneOf, links, zoom, onMove, onProgress, onOpen, height, nameHeader, }: RecordGanttProps<T>): import("react").JSX.Element;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { TimelineToolbar } from "../timeline/TimelineToolbar.js";
|
|
4
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
5
|
+
import { Gantt, Willow } from "@svar-ui/react-gantt";
|
|
6
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
/* The two rows of the time scale per zoom, written with `Intl` in the system's language
|
|
8
|
+
(SVAR's own format strings would ignore it), and how wide one cell of the finer row is. */
|
|
9
|
+
function scalesOf(zoom, locale) {
|
|
10
|
+
const say = (options) => (date) => new Intl.DateTimeFormat(locale, options).format(date);
|
|
11
|
+
const week = (date) => {
|
|
12
|
+
const first = new Date(date.getFullYear(), 0, 1);
|
|
13
|
+
return `S${Math.ceil(((+date - +first) / 86_400_000 + first.getDay() + 1) / 7)}`;
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
day: {
|
|
17
|
+
cellWidth: 36,
|
|
18
|
+
scales: [
|
|
19
|
+
{ unit: "month", step: 1, format: say({ month: "long", year: "numeric" }) },
|
|
20
|
+
{ unit: "day", step: 1, format: say({ day: "numeric" }) },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
week: {
|
|
24
|
+
cellWidth: 72,
|
|
25
|
+
scales: [
|
|
26
|
+
{ unit: "month", step: 1, format: say({ month: "long", year: "numeric" }) },
|
|
27
|
+
{ unit: "week", step: 1, format: week },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
month: {
|
|
31
|
+
cellWidth: 96,
|
|
32
|
+
scales: [
|
|
33
|
+
{ unit: "year", step: 1, format: say({ year: "numeric" }) },
|
|
34
|
+
{ unit: "month", step: 1, format: say({ month: "short" }) },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
}[zoom];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Records on a timeline — Odoo's Gantt view: projects, tasks, manufacturing orders.
|
|
41
|
+
*
|
|
42
|
+
* Context: drawn by SVAR React Gantt (MIT core: move, resize, progress, dependencies, zoom),
|
|
43
|
+
* dressed by `tokens.css` (its Willow theme's `--wx-*` variables point at web-ui's tokens).
|
|
44
|
+
* The chart keeps its own state while dragging; every finished change is reported (`onMove`,
|
|
45
|
+
* `onProgress`) and the app passes the saved `items` back, which the chart re-reads. Without
|
|
46
|
+
* `onMove` / `onProgress` it is read-only. Auto-scheduling, critical path and resource load are
|
|
47
|
+
* SVAR's PRO edition: a licence away, not built here.
|
|
48
|
+
*/
|
|
49
|
+
export function RecordGantt({ items, keyOf, titleOf, startOf, endOf, progressOf, parentOf, milestoneOf, links = [], zoom = "day", onMove, onProgress, onOpen, height = "28rem", nameHeader, }) {
|
|
50
|
+
const copy = useUiCopy();
|
|
51
|
+
/* The toolbar's: the scale starts at `zoom`, the closeness in the middle. */
|
|
52
|
+
const [scale, setScale] = useState(zoom);
|
|
53
|
+
const [closeness, setCloseness] = useState(3);
|
|
54
|
+
const time = scalesOf(scale, copy.locale);
|
|
55
|
+
const cellWidth = Math.round(time.cellWidth * ([0.5, 0.75, 1, 1.5, 2.25][closeness - 1] ?? 1));
|
|
56
|
+
const root = useRef(null);
|
|
57
|
+
const [fullscreen, setFullscreen] = useState(false);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
const follow = () => setFullscreen(document.fullscreenElement === root.current);
|
|
60
|
+
document.addEventListener("fullscreenchange", follow);
|
|
61
|
+
return () => document.removeEventListener("fullscreenchange", follow);
|
|
62
|
+
}, []);
|
|
63
|
+
const byId = useMemo(() => new Map(items.map((item) => [keyOf(item), item])), [items, keyOf]);
|
|
64
|
+
const parents = new Set(items.map((item) => parentOf?.(item)).filter(Boolean));
|
|
65
|
+
const tasks = items.map((item) => {
|
|
66
|
+
const id = keyOf(item);
|
|
67
|
+
const parent = parentOf?.(item);
|
|
68
|
+
return {
|
|
69
|
+
id,
|
|
70
|
+
text: titleOf(item),
|
|
71
|
+
start: startOf(item),
|
|
72
|
+
end: endOf(item),
|
|
73
|
+
...(progressOf === undefined ? {} : { progress: progressOf(item) }),
|
|
74
|
+
...(parent === undefined ? {} : { parent }),
|
|
75
|
+
type: milestoneOf?.(item) === true ? "milestone" : parents.has(id) ? "summary" : "task",
|
|
76
|
+
open: true,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
const editable = onMove !== undefined || onProgress !== undefined;
|
|
80
|
+
/* The chart reports through its store's actions: `update-task` for a move, a stretch or the
|
|
81
|
+
progress handle — the finished one, not every pixel of the drag (`inProgress`). */
|
|
82
|
+
const init = (api) => {
|
|
83
|
+
api.on("update-task", (ev) => {
|
|
84
|
+
if (ev.inProgress === true)
|
|
85
|
+
return;
|
|
86
|
+
const item = byId.get(String(ev.id));
|
|
87
|
+
if (item === undefined)
|
|
88
|
+
return;
|
|
89
|
+
const { start, end, progress } = ev.task;
|
|
90
|
+
if (start instanceof Date && end instanceof Date)
|
|
91
|
+
onMove?.(item, start, end);
|
|
92
|
+
if (typeof progress === "number")
|
|
93
|
+
onProgress?.(item, progress);
|
|
94
|
+
});
|
|
95
|
+
api.on("select-task", (ev) => {
|
|
96
|
+
const item = byId.get(String(ev.id));
|
|
97
|
+
if (item !== undefined)
|
|
98
|
+
onOpen?.(item);
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
return (_jsxs("div", { ref: root, className: "flex min-w-0 flex-col overflow-hidden rounded-box border border-line bg-surface", style: { height }, children: [_jsx(TimelineToolbar, { scale: scale, onScaleChange: (next) => {
|
|
102
|
+
if (next !== "year")
|
|
103
|
+
setScale(next);
|
|
104
|
+
}, scales: ["day", "week", "month"], zoom: closeness, onZoomChange: setCloseness, fullscreen: fullscreen, onFullscreenChange: (next) => {
|
|
105
|
+
if (next)
|
|
106
|
+
void root.current?.requestFullscreen?.();
|
|
107
|
+
else if (document.fullscreenElement !== null)
|
|
108
|
+
void document.exitFullscreen();
|
|
109
|
+
} }), _jsx("div", { className: "min-h-0 flex-1", children: _jsx(Willow, { fonts: false, children: _jsx(Gantt, { tasks: tasks, links: links.map((link) => ({ ...link, type: link.type ?? "e2s" })), scales: time.scales, cellWidth: cellWidth, readonly: !editable, columns: [{ id: "text", header: nameHeader ?? "", width: 260 }], init: init }) }) })] }));
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type GanttLink, type GanttZoom, RecordGantt, type RecordGanttProps, } from "./RecordGantt.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RecordGantt, } from "./RecordGantt.js";
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
export * from "./activity/index.js";
|
|
1
2
|
export * from "./assist/index.js";
|
|
2
|
-
export * from "./
|
|
3
|
+
export * from "./calendar/index.js";
|
|
3
4
|
export * from "./form/index.js";
|
|
4
5
|
export * from "./frame/index.js";
|
|
6
|
+
export * from "./gantt/index.js";
|
|
5
7
|
export * from "./lines/index.js";
|
|
6
8
|
export * from "./listing/index.js";
|
|
7
9
|
export * from "./navigation/index.js";
|
|
8
10
|
export * from "./record/index.js";
|
|
9
11
|
export * from "./search/index.js";
|
|
10
12
|
export * from "./shell/index.js";
|
|
13
|
+
export * from "./timeline/index.js";
|
package/dist/components/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/* COMPONENTS — primitives composed to answer ONE question of the product, by family. */
|
|
2
|
+
export * from "./activity/index.js";
|
|
2
3
|
export * from "./assist/index.js";
|
|
3
|
-
export * from "./
|
|
4
|
+
export * from "./calendar/index.js";
|
|
4
5
|
export * from "./form/index.js";
|
|
5
6
|
export * from "./frame/index.js";
|
|
7
|
+
export * from "./gantt/index.js";
|
|
6
8
|
export * from "./lines/index.js";
|
|
7
9
|
export * from "./listing/index.js";
|
|
8
10
|
export * from "./navigation/index.js";
|
|
9
11
|
export * from "./record/index.js";
|
|
10
12
|
export * from "./search/index.js";
|
|
11
13
|
export * from "./shell/index.js";
|
|
14
|
+
export * from "./timeline/index.js";
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type DialogContext } from "../../primitives/dialog/Dialog.js";
|
|
2
2
|
import { type FormMode } from "../../primitives/field/mode.js";
|
|
3
|
+
import type { FlagTone } from "../../primitives/notice/Flag.js";
|
|
3
4
|
import type { DataColumn } from "../../primitives/table/DataTable.js";
|
|
4
5
|
import { type EditColumn, type EditLayout } from "../../primitives/table/EditableTable.js";
|
|
5
|
-
import { type TotalsRow } from "../../primitives/value/Totals.js";
|
|
6
|
+
import { type TotalsProps, type TotalsRow } from "../../primitives/value/Totals.js";
|
|
6
7
|
import { type ReactNode } from "react";
|
|
7
8
|
/**
|
|
8
9
|
* A column of the lines. The same contract as `DataTable`'s columns —`value` is
|
|
@@ -10,11 +11,25 @@ import { type ReactNode } from "react";
|
|
|
10
11
|
* reads as plain text— plus `edit`, the control that replaces the cell when the
|
|
11
12
|
* row is being edited in place.
|
|
12
13
|
*/
|
|
13
|
-
export type LineItemColumn<T> = Pick<DataColumn<T>, "id" | "header" | "value" | "cell" | "text" | "numeric" | "width"> & Pick<EditColumn<T>, "stack" | "brief"> & {
|
|
14
|
+
export type LineItemColumn<T> = Pick<DataColumn<T>, "id" | "header" | "value" | "cell" | "text" | "numeric" | "width" | "optional" | "defaultHidden"> & Pick<EditColumn<T>, "stack" | "brief"> & {
|
|
14
15
|
/** The control while the row is edited in place. Without it the cell stays read
|
|
15
16
|
* only — a computed amount, a code. Only simple values belong here; the whole
|
|
16
17
|
* line is edited in the `editor`. */
|
|
17
18
|
edit?: (line: T, set: (next: T) => void) => ReactNode;
|
|
19
|
+
/** What the lines of a section add up to in this column, already formatted. Only
|
|
20
|
+
* with `structure`; without it the section says nothing in this column. */
|
|
21
|
+
subtotal?: (lines: readonly T[]) => ReactNode;
|
|
22
|
+
};
|
|
23
|
+
/** Which lines are sections or notes, and how their text is read and written. See
|
|
24
|
+
* «Sections and notes» in the file's header. */
|
|
25
|
+
export type LineStructure<T> = {
|
|
26
|
+
kind: (line: T) => "line" | "section" | "note";
|
|
27
|
+
text: (line: T) => string;
|
|
28
|
+
withText: (line: T, text: string) => T;
|
|
29
|
+
/** A blank section; enables «Agregar una sección». */
|
|
30
|
+
newSection?: () => T;
|
|
31
|
+
/** A blank note; enables «Agregar una nota». */
|
|
32
|
+
newNote?: () => T;
|
|
18
33
|
};
|
|
19
34
|
/** What the popup editor receives besides the draft. */
|
|
20
35
|
export type LineEditorState = {
|
|
@@ -56,11 +71,36 @@ export type LineItemsProps<T> = {
|
|
|
56
71
|
/** The search shows up only with MORE lines than this. Four lines are read, not
|
|
57
72
|
* searched; a search box over them is a control that answers nothing. */
|
|
58
73
|
searchFrom?: number;
|
|
59
|
-
/** The money summary under the lines, on the right: the system's `Totals`.
|
|
60
|
-
|
|
74
|
+
/** The money summary under the lines, on the right: the system's `Totals`. Its rows,
|
|
75
|
+
* or all of it — payments deducted, the balance due, the credits that can be applied. */
|
|
76
|
+
totals?: readonly TotalsRow[] | TotalsProps;
|
|
61
77
|
/** `auto` (the default) draws the lines as stacked cards below
|
|
62
78
|
* `--container-handheld`; `table` and `stacked` force one. */
|
|
63
79
|
layout?: EditLayout;
|
|
80
|
+
/** How «Agregar una línea» adds: `editor` opens the blank line in the dialog;
|
|
81
|
+
* `inline` puts it in the table, open, under the others (see the file's header).
|
|
82
|
+
* By default the dialog when there is an `editor`, the table when there is not. */
|
|
83
|
+
adding?: "inline" | "editor";
|
|
84
|
+
/** Open the whole line somewhere else than this block's dialog — a modal of the workspace, so
|
|
85
|
+
* the line opens as «S00142 › Línea 3» and can go deeper (its product, its lot) without
|
|
86
|
+
* a modal over a modal. When given, it replaces `editor` for existing lines. */
|
|
87
|
+
onOpenLine?: (id: string) => void;
|
|
88
|
+
/** A warning about one line, on the line: «Sin existencias», «Precio bajo el costo».
|
|
89
|
+
* `EditableTable`'s `flag`, passed through. */
|
|
90
|
+
flag?: (line: T) => {
|
|
91
|
+
tone: FlagTone;
|
|
92
|
+
label: string;
|
|
93
|
+
detail?: ReactNode;
|
|
94
|
+
owner?: string;
|
|
95
|
+
} | null;
|
|
96
|
+
/** Sections and notes among the lines. See the file's header. */
|
|
97
|
+
structure?: LineStructure<T>;
|
|
98
|
+
/** The lines' order is the document's (Odoo's `sequence`): a grip at the start of each line
|
|
99
|
+
* drags it, Alt + ↑ / ↓ moves it. `EditableTable`'s `onReorder`, passed through. */
|
|
100
|
+
onReorder?: (id: string, before: string | null) => void;
|
|
101
|
+
/** Which columns are on screen, when the owner remembers it (a preference). */
|
|
102
|
+
visibleColumnIds?: readonly string[];
|
|
103
|
+
onVisibleColumnIdsChange?: (ids: readonly string[]) => void;
|
|
64
104
|
};
|
|
65
105
|
/**
|
|
66
106
|
* The lines of a document inside its record: a count and a search above, the lines
|
|
@@ -71,4 +111,4 @@ export type LineItemsProps<T> = {
|
|
|
71
111
|
* removing asked first, what an empty document said. One component, built on
|
|
72
112
|
* `EditableTable` and `Dialog`, makes the one2many the same block everywhere.
|
|
73
113
|
*/
|
|
74
|
-
export declare function LineItems<T>({ lines, columns, getLineId, lineLabel, mode, onChange, onAdd, newLine, onRemove, onRestore, editor, isValid, context, searchText, searchFrom, totals, layout, }: LineItemsProps<T>): import("react").JSX.Element;
|
|
114
|
+
export declare function LineItems<T>({ lines, columns, getLineId, lineLabel, mode, onChange, onAdd, newLine, onRemove, onRestore, editor, isValid, context, searchText, searchFrom, totals, layout, adding, onOpenLine, flag, structure, onReorder, visibleColumnIds, onVisibleColumnIdsChange, }: LineItemsProps<T>): import("react").JSX.Element;
|