@open-mercato/ui 0.6.2-develop.3406.1.2b403f40da → 0.6.2-develop.3446.1.bd060c6017
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/.turbo/turbo-build.log +1 -1
- package/dist/ai/AiAssistantLauncher.js +11 -10
- package/dist/ai/AiAssistantLauncher.js.map +2 -2
- package/dist/ai/AiChat.js +218 -83
- package/dist/ai/AiChat.js.map +2 -2
- package/dist/ai/AiIcon.js +1 -1
- package/dist/ai/AiIcon.js.map +2 -2
- package/dist/ai/parts/FieldDiffCard.js +4 -3
- package/dist/ai/parts/FieldDiffCard.js.map +2 -2
- package/dist/ai/useAiChat.js +98 -2
- package/dist/ai/useAiChat.js.map +2 -2
- package/dist/backend/DataTable.js +4 -13
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/primitives/date-format.js +53 -0
- package/dist/primitives/date-format.js.map +7 -0
- package/dist/primitives/date-picker.js +3 -26
- package/dist/primitives/date-picker.js.map +2 -2
- package/dist/primitives/date-range-picker.js +3 -22
- package/dist/primitives/date-range-picker.js.map +2 -2
- package/package.json +4 -4
- package/src/ai/AiAssistantLauncher.tsx +11 -10
- package/src/ai/AiChat.tsx +200 -26
- package/src/ai/AiIcon.tsx +1 -1
- package/src/ai/__tests__/AiChat.task-plan.test.tsx +356 -0
- package/src/ai/__tests__/AiChat.test.tsx +44 -2
- package/src/ai/parts/FieldDiffCard.tsx +4 -3
- package/src/ai/parts/__tests__/FieldDiffCard.test.tsx +24 -0
- package/src/ai/parts/types.ts +3 -0
- package/src/ai/useAiChat.ts +154 -1
- package/src/backend/DataTable.tsx +12 -22
- package/src/primitives/__tests__/date-format.test.ts +43 -0
- package/src/primitives/date-format.ts +60 -0
- package/src/primitives/date-picker.tsx +5 -17
- package/src/primitives/date-range-picker.tsx +3 -13
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { format as formatDateFns } from "date-fns";
|
|
2
|
+
const DAY_FIRST_LOCALE_CODES = /* @__PURE__ */ new Set([
|
|
3
|
+
"pl",
|
|
4
|
+
"de",
|
|
5
|
+
"fr",
|
|
6
|
+
"es",
|
|
7
|
+
"it",
|
|
8
|
+
"pt",
|
|
9
|
+
"nl",
|
|
10
|
+
"ru",
|
|
11
|
+
"cs",
|
|
12
|
+
"sk",
|
|
13
|
+
"hu",
|
|
14
|
+
"ro"
|
|
15
|
+
]);
|
|
16
|
+
const SYSTEM_FORMAT_VALUES = /* @__PURE__ */ new Set(["auto", "default", "locale", "system"]);
|
|
17
|
+
function getLocaleCode(locale) {
|
|
18
|
+
if (!locale) return "";
|
|
19
|
+
if (typeof locale === "string") return locale.split("-")[0]?.toLowerCase() ?? "";
|
|
20
|
+
return locale.code?.split("-")[0]?.toLowerCase() ?? "";
|
|
21
|
+
}
|
|
22
|
+
function normalizeDateFormatPattern(value) {
|
|
23
|
+
const trimmed = value?.trim();
|
|
24
|
+
if (!trimmed) return null;
|
|
25
|
+
if (SYSTEM_FORMAT_VALUES.has(trimmed.toLowerCase())) return null;
|
|
26
|
+
return trimmed.replace(/YYYY/g, "yyyy").replace(/YY/g, "yy").replace(/DD/g, "dd");
|
|
27
|
+
}
|
|
28
|
+
function deriveDateDisplayFormat(locale) {
|
|
29
|
+
const code = getLocaleCode(locale);
|
|
30
|
+
return code && DAY_FIRST_LOCALE_CODES.has(code) ? "d MMM yyyy" : "MMM d, yyyy";
|
|
31
|
+
}
|
|
32
|
+
function resolvePublicDateFormat(locale, explicitFormat) {
|
|
33
|
+
return normalizeDateFormatPattern(explicitFormat) ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_FORMAT) ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_FORMAT) ?? deriveDateDisplayFormat(locale);
|
|
34
|
+
}
|
|
35
|
+
function resolvePublicDateTimeFormat(locale, explicitFormat) {
|
|
36
|
+
const dateFormat = deriveDateDisplayFormat(locale);
|
|
37
|
+
return normalizeDateFormatPattern(explicitFormat) ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_TIME_FORMAT) ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_TIME_FORMAT) ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_FORMAT) ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_FORMAT) ?? `${dateFormat} HH:mm`;
|
|
38
|
+
}
|
|
39
|
+
function formatWithPublicDateFormat(date, format, locale) {
|
|
40
|
+
try {
|
|
41
|
+
return formatDateFns(date, format, locale ? { locale } : void 0);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
deriveDateDisplayFormat,
|
|
48
|
+
formatWithPublicDateFormat,
|
|
49
|
+
normalizeDateFormatPattern,
|
|
50
|
+
resolvePublicDateFormat,
|
|
51
|
+
resolvePublicDateTimeFormat
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=date-format.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/primitives/date-format.ts"],
|
|
4
|
+
"sourcesContent": ["import { format as formatDateFns } from 'date-fns'\nimport type { Locale } from 'date-fns'\n\ntype LocaleLike = Locale | string | null | undefined\n\nconst DAY_FIRST_LOCALE_CODES = new Set([\n 'pl', 'de', 'fr', 'es', 'it', 'pt', 'nl', 'ru', 'cs', 'sk', 'hu', 'ro',\n])\n\nconst SYSTEM_FORMAT_VALUES = new Set(['auto', 'default', 'locale', 'system'])\n\nfunction getLocaleCode(locale?: LocaleLike): string {\n if (!locale) return ''\n if (typeof locale === 'string') return locale.split('-')[0]?.toLowerCase() ?? ''\n return locale.code?.split('-')[0]?.toLowerCase() ?? ''\n}\n\nexport function normalizeDateFormatPattern(value: string | null | undefined): string | null {\n const trimmed = value?.trim()\n if (!trimmed) return null\n if (SYSTEM_FORMAT_VALUES.has(trimmed.toLowerCase())) return null\n return trimmed\n .replace(/YYYY/g, 'yyyy')\n .replace(/YY/g, 'yy')\n .replace(/DD/g, 'dd')\n}\n\nexport function deriveDateDisplayFormat(locale?: LocaleLike): string {\n const code = getLocaleCode(locale)\n return code && DAY_FIRST_LOCALE_CODES.has(code) ? 'd MMM yyyy' : 'MMM d, yyyy'\n}\n\nexport function resolvePublicDateFormat(locale?: LocaleLike, explicitFormat?: string | null): string {\n return (\n normalizeDateFormatPattern(explicitFormat)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_FORMAT)\n ?? deriveDateDisplayFormat(locale)\n )\n}\n\nexport function resolvePublicDateTimeFormat(locale?: LocaleLike, explicitFormat?: string | null): string {\n const dateFormat = deriveDateDisplayFormat(locale)\n return (\n normalizeDateFormatPattern(explicitFormat)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_TIME_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_TIME_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_OM_DATE_FORMAT)\n ?? normalizeDateFormatPattern(process.env.NEXT_PUBLIC_DATE_FORMAT)\n ?? `${dateFormat} HH:mm`\n )\n}\n\nexport function formatWithPublicDateFormat(date: Date, format: string, locale?: Locale): string | null {\n try {\n return formatDateFns(date, format, locale ? { locale } : undefined)\n } catch {\n return null\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,UAAU,qBAAqB;AAKxC,MAAM,yBAAyB,oBAAI,IAAI;AAAA,EACrC;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AACpE,CAAC;AAED,MAAM,uBAAuB,oBAAI,IAAI,CAAC,QAAQ,WAAW,UAAU,QAAQ,CAAC;AAE5E,SAAS,cAAc,QAA6B;AAClD,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,WAAW,SAAU,QAAO,OAAO,MAAM,GAAG,EAAE,CAAC,GAAG,YAAY,KAAK;AAC9E,SAAO,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,YAAY,KAAK;AACtD;AAEO,SAAS,2BAA2B,OAAiD;AAC1F,QAAM,UAAU,OAAO,KAAK;AAC5B,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,qBAAqB,IAAI,QAAQ,YAAY,CAAC,EAAG,QAAO;AAC5D,SAAO,QACJ,QAAQ,SAAS,MAAM,EACvB,QAAQ,OAAO,IAAI,EACnB,QAAQ,OAAO,IAAI;AACxB;AAEO,SAAS,wBAAwB,QAA6B;AACnE,QAAM,OAAO,cAAc,MAAM;AACjC,SAAO,QAAQ,uBAAuB,IAAI,IAAI,IAAI,eAAe;AACnE;AAEO,SAAS,wBAAwB,QAAqB,gBAAwC;AACnG,SACE,2BAA2B,cAAc,KACtC,2BAA2B,QAAQ,IAAI,0BAA0B,KACjE,2BAA2B,QAAQ,IAAI,uBAAuB,KAC9D,wBAAwB,MAAM;AAErC;AAEO,SAAS,4BAA4B,QAAqB,gBAAwC;AACvG,QAAM,aAAa,wBAAwB,MAAM;AACjD,SACE,2BAA2B,cAAc,KACtC,2BAA2B,QAAQ,IAAI,+BAA+B,KACtE,2BAA2B,QAAQ,IAAI,4BAA4B,KACnE,2BAA2B,QAAQ,IAAI,0BAA0B,KACjE,2BAA2B,QAAQ,IAAI,uBAAuB,KAC9D,GAAG,UAAU;AAEpB;AAEO,SAAS,2BAA2B,MAAY,QAAgB,QAAgC;AACrG,MAAI;AACF,WAAO,cAAc,MAAM,QAAQ,SAAS,EAAE,OAAO,IAAI,MAAS;AAAA,EACpE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { format } from "date-fns";
|
|
5
4
|
import { CalendarIcon } from "lucide-react";
|
|
6
5
|
import { cn } from "@open-mercato/shared/lib/utils";
|
|
7
6
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
@@ -10,25 +9,7 @@ import { Calendar } from "./calendar.js";
|
|
|
10
9
|
import { Button } from "./button.js";
|
|
11
10
|
import { LinkButton } from "./link-button.js";
|
|
12
11
|
import { TimeInput } from "../backend/inputs/TimeInput.js";
|
|
13
|
-
|
|
14
|
-
"pl",
|
|
15
|
-
"de",
|
|
16
|
-
"fr",
|
|
17
|
-
"es",
|
|
18
|
-
"it",
|
|
19
|
-
"pt",
|
|
20
|
-
"nl",
|
|
21
|
-
"ru",
|
|
22
|
-
"cs",
|
|
23
|
-
"sk",
|
|
24
|
-
"hu",
|
|
25
|
-
"ro"
|
|
26
|
-
]);
|
|
27
|
-
function deriveDisplayFormat(locale, withTime) {
|
|
28
|
-
const code = locale?.code?.split("-")[0]?.toLowerCase() ?? "";
|
|
29
|
-
const dateFmt = code && DAY_FIRST_LOCALE_CODES.has(code) ? "d MMM yyyy" : "MMM d, yyyy";
|
|
30
|
-
return withTime ? `${dateFmt} HH:mm` : dateFmt;
|
|
31
|
-
}
|
|
12
|
+
import { formatWithPublicDateFormat, resolvePublicDateFormat, resolvePublicDateTimeFormat } from "./date-format.js";
|
|
32
13
|
function extractTime(date) {
|
|
33
14
|
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
34
15
|
}
|
|
@@ -80,15 +61,11 @@ function DatePicker({
|
|
|
80
61
|
React.useEffect(() => {
|
|
81
62
|
if (open) setDraft(value ?? null);
|
|
82
63
|
}, [open, value]);
|
|
83
|
-
const resolvedFormat = displayFormat
|
|
64
|
+
const resolvedFormat = withTime ? resolvePublicDateTimeFormat(locale, displayFormat) : resolvePublicDateFormat(locale, displayFormat);
|
|
84
65
|
const placeholderText = placeholder ?? (withTime ? t("ui.dateTimePicker.placeholder", "Pick date and time") : t("ui.datePicker.placeholder", "Pick a date"));
|
|
85
66
|
const formattedValue = React.useMemo(() => {
|
|
86
67
|
if (!value) return null;
|
|
87
|
-
|
|
88
|
-
return format(value, resolvedFormat, locale ? { locale } : void 0);
|
|
89
|
-
} catch {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
68
|
+
return formatWithPublicDateFormat(value, resolvedFormat, locale);
|
|
92
69
|
}, [value, resolvedFormat, locale]);
|
|
93
70
|
const handleDaySelect = React.useCallback(
|
|
94
71
|
(day) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/primitives/date-picker.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { format } from 'date-fns'\nimport type { Locale } from 'date-fns'\nimport { CalendarIcon } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Popover, PopoverContent, PopoverTrigger } from './popover'\nimport { Calendar } from './calendar'\nimport { Button } from './button'\nimport { LinkButton } from './link-button'\nimport { TimeInput } from '../backend/inputs/TimeInput'\n\nexport type DatePickerFooter = 'apply-cancel' | 'today-clear' | 'none'\n\nexport type DatePickerProps = {\n value?: Date | null\n onChange: (value: Date | null) => void\n placeholder?: string\n size?: 'sm' | 'default'\n disabled?: boolean\n readOnly?: boolean\n /**\n * Footer mode in the popover.\n * - `'apply-cancel'` (default, Figma-aligned): selecting a day stages a draft;\n * Apply commits and closes; Cancel reverts and closes.\n * - `'today-clear'`: legacy behavior \u2014 Today commits today, Clear sets null.\n * - `'none'`: no footer; selecting a day commits immediately and closes.\n */\n footer?: DatePickerFooter\n /**\n * Whether selecting a day commits the value and closes the popover.\n * - In `footer='apply-cancel'`: ignored (commit happens via Apply).\n * - In `footer='today-clear'`: default `true` for backward compatibility.\n * - In `footer='none'`: always `true` (popover always closes after selection).\n */\n closeOnSelect?: boolean\n /**\n * Whether to show the \"Today\" link in `footer='today-clear'` mode. Default `true`.\n * Only meaningful when `footer='today-clear'`.\n */\n showTodayButton?: boolean\n /**\n * Whether to show the \"Clear\" link in `footer='today-clear'` mode. Default `true`.\n * Only meaningful when `footer='today-clear'`.\n */\n showClearButton?: boolean\n withTime?: boolean\n minuteStep?: number\n align?: 'start' | 'center' | 'end'\n minDate?: Date\n maxDate?: Date\n locale?: Locale\n displayFormat?: string\n className?: string\n popoverClassName?: string\n id?: string\n name?: string\n required?: boolean\n 'aria-label'?: string\n 'aria-describedby'?: string\n}\n\nconst DAY_FIRST_LOCALE_CODES = new Set([\n 'pl', 'de', 'fr', 'es', 'it', 'pt', 'nl', 'ru', 'cs', 'sk', 'hu', 'ro',\n])\n\nfunction deriveDisplayFormat(locale: Locale | undefined, withTime: boolean): string {\n const code = locale?.code?.split('-')[0]?.toLowerCase() ?? ''\n const dateFmt = code && DAY_FIRST_LOCALE_CODES.has(code) ? 'd MMM yyyy' : 'MMM d, yyyy'\n return withTime ? `${dateFmt} HH:mm` : dateFmt\n}\n\nfunction extractTime(date: Date): string {\n return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`\n}\n\nfunction applyTimeToDate(base: Date, time: string): Date {\n const [hourStr = '0', minuteStr = '0'] = time.split(':')\n const hour = parseInt(hourStr, 10)\n const minute = parseInt(minuteStr, 10)\n const next = new Date(base)\n next.setHours(isNaN(hour) ? 0 : hour, isNaN(minute) ? 0 : minute, 0, 0)\n return next\n}\n\nfunction startOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(0, 0, 0, 0)\n return next\n}\n\nexport function DatePicker({\n value,\n onChange,\n placeholder,\n size = 'default',\n disabled = false,\n readOnly = false,\n footer = 'apply-cancel',\n closeOnSelect,\n showTodayButton = true,\n showClearButton = true,\n withTime = false,\n minuteStep = 1,\n align = 'start',\n minDate,\n maxDate,\n locale,\n displayFormat,\n className,\n popoverClassName,\n id,\n name,\n required,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n}: DatePickerProps) {\n const resolvedCloseOnSelect = closeOnSelect ?? footer === 'today-clear'\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [draft, setDraft] = React.useState<Date | null>(value ?? null)\n\n const isInteractive = !disabled && !readOnly\n const useDraft = footer === 'apply-cancel'\n\n React.useEffect(() => {\n if (open) setDraft(value ?? null)\n }, [open, value])\n\n const resolvedFormat = displayFormat ?? deriveDisplayFormat(locale, withTime)\n const placeholderText = placeholder ?? (\n withTime\n ? t('ui.dateTimePicker.placeholder', 'Pick date and time')\n : t('ui.datePicker.placeholder', 'Pick a date')\n )\n\n const formattedValue = React.useMemo(() => {\n if (!value) return null\n try {\n return format(value, resolvedFormat, locale ? { locale } : undefined)\n } catch {\n return null\n }\n }, [value, resolvedFormat, locale])\n\n const handleDaySelect = React.useCallback(\n (day: Date | undefined) => {\n if (!day || !isInteractive) return\n const reference = withTime ? (useDraft ? draft : value) : null\n const base = withTime\n ? applyTimeToDate(day, reference ? extractTime(reference) : '00:00')\n : startOfDay(day)\n if (useDraft) {\n setDraft(base)\n } else {\n onChange(base)\n if (footer === 'none' || resolvedCloseOnSelect) setOpen(false)\n }\n },\n [isInteractive, withTime, useDraft, draft, value, footer, resolvedCloseOnSelect, onChange],\n )\n\n const handleTimeChange = React.useCallback(\n (time: string) => {\n const target = useDraft ? draft : value\n if (!target) return\n const next = applyTimeToDate(target, time)\n if (useDraft) setDraft(next)\n else onChange(next)\n },\n [useDraft, draft, value, onChange],\n )\n\n const handleApply = React.useCallback(() => {\n onChange(draft)\n setOpen(false)\n }, [draft, onChange])\n\n const handleCancel = React.useCallback(() => {\n setDraft(value ?? null)\n setOpen(false)\n }, [value])\n\n const handleToday = React.useCallback(() => {\n const today = withTime ? new Date() : startOfDay(new Date())\n onChange(today)\n setOpen(false)\n }, [withTime, onChange])\n\n const handleClear = React.useCallback(() => {\n onChange(null)\n setOpen(false)\n }, [onChange])\n\n const disabledMatcher = React.useMemo(() => {\n if (!minDate && !maxDate) return undefined\n const matchers: import('react-day-picker').Matcher[] = []\n if (minDate) matchers.push({ before: minDate })\n if (maxDate) matchers.push({ after: maxDate })\n return matchers\n }, [minDate, maxDate])\n\n const heightClass = size === 'sm' ? 'h-8 px-2.5 text-xs' : 'h-9 px-3 text-sm'\n const selectedDate = useDraft ? draft : value\n\n return (\n <Popover open={open} onOpenChange={isInteractive ? setOpen : undefined}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n id={id}\n name={name}\n aria-label={ariaLabel}\n aria-describedby={ariaDescribedBy}\n aria-haspopup=\"dialog\"\n aria-required={required ? true : undefined}\n data-crud-focus-target=\"\"\n data-slot=\"date-picker-trigger\"\n disabled={disabled}\n className={cn(\n 'w-full inline-flex items-center gap-2 rounded-md border border-input bg-background shadow-xs transition-colors text-left',\n 'focus-visible:outline-none focus-visible:shadow-focus focus-visible:border-foreground',\n 'hover:bg-muted/40',\n 'disabled:bg-bg-disabled disabled:border-border-disabled disabled:shadow-none disabled:hover:bg-bg-disabled disabled:cursor-not-allowed',\n 'aria-invalid:border-destructive',\n heightClass,\n readOnly && 'cursor-default opacity-70',\n !formattedValue && 'text-muted-foreground',\n className,\n )}\n onClick={isInteractive ? undefined : (event) => event.preventDefault()}\n >\n <CalendarIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <span className=\"flex-1 truncate\">\n {formattedValue ?? placeholderText}\n </span>\n </button>\n </PopoverTrigger>\n <PopoverContent align={align} className={cn('w-auto p-0', popoverClassName)}>\n <Calendar\n mode=\"single\"\n selected={selectedDate ?? undefined}\n onSelect={handleDaySelect}\n locale={locale}\n disabled={disabledMatcher}\n initialFocus\n />\n {withTime && (\n <div className=\"flex items-center gap-2 border-t px-3 py-2\">\n <span className=\"text-sm text-muted-foreground shrink-0\">\n {t('ui.datePicker.timeLabel', 'Time')}:\n </span>\n <TimeInput\n value={selectedDate ? extractTime(selectedDate) : undefined}\n onChange={handleTimeChange}\n minuteStep={minuteStep}\n disabled={!isInteractive || !selectedDate}\n />\n </div>\n )}\n {footer === 'apply-cancel' && (\n <div className=\"flex items-center justify-end gap-2 border-t px-3 py-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleCancel}>\n {t('ui.datePicker.cancelButton', 'Cancel')}\n </Button>\n <Button type=\"button\" size=\"sm\" onClick={handleApply}>\n {t('ui.datePicker.applyButton', 'Apply')}\n </Button>\n </div>\n )}\n {footer === 'today-clear' && (showTodayButton || showClearButton) && (\n <div className=\"flex items-center justify-between gap-2 border-t px-3 py-2\">\n {showTodayButton ? (\n <LinkButton type=\"button\" variant=\"primary\" onClick={handleToday}>\n {t('ui.datePicker.todayButton', 'Today')}\n </LinkButton>\n ) : null}\n {showClearButton ? (\n <LinkButton type=\"button\" variant=\"gray\" onClick={handleClear} className=\"ml-auto\">\n {t('ui.datePicker.clearButton', 'Clear')}\n </LinkButton>\n ) : null}\n </div>\n )}\n </PopoverContent>\n </Popover>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { Locale } from 'date-fns'\nimport { CalendarIcon } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Popover, PopoverContent, PopoverTrigger } from './popover'\nimport { Calendar } from './calendar'\nimport { Button } from './button'\nimport { LinkButton } from './link-button'\nimport { TimeInput } from '../backend/inputs/TimeInput'\nimport { formatWithPublicDateFormat, resolvePublicDateFormat, resolvePublicDateTimeFormat } from './date-format'\n\nexport type DatePickerFooter = 'apply-cancel' | 'today-clear' | 'none'\n\nexport type DatePickerProps = {\n value?: Date | null\n onChange: (value: Date | null) => void\n placeholder?: string\n size?: 'sm' | 'default'\n disabled?: boolean\n readOnly?: boolean\n /**\n * Footer mode in the popover.\n * - `'apply-cancel'` (default, Figma-aligned): selecting a day stages a draft;\n * Apply commits and closes; Cancel reverts and closes.\n * - `'today-clear'`: legacy behavior \u2014 Today commits today, Clear sets null.\n * - `'none'`: no footer; selecting a day commits immediately and closes.\n */\n footer?: DatePickerFooter\n /**\n * Whether selecting a day commits the value and closes the popover.\n * - In `footer='apply-cancel'`: ignored (commit happens via Apply).\n * - In `footer='today-clear'`: default `true` for backward compatibility.\n * - In `footer='none'`: always `true` (popover always closes after selection).\n */\n closeOnSelect?: boolean\n /**\n * Whether to show the \"Today\" link in `footer='today-clear'` mode. Default `true`.\n * Only meaningful when `footer='today-clear'`.\n */\n showTodayButton?: boolean\n /**\n * Whether to show the \"Clear\" link in `footer='today-clear'` mode. Default `true`.\n * Only meaningful when `footer='today-clear'`.\n */\n showClearButton?: boolean\n withTime?: boolean\n minuteStep?: number\n align?: 'start' | 'center' | 'end'\n minDate?: Date\n maxDate?: Date\n locale?: Locale\n displayFormat?: string\n className?: string\n popoverClassName?: string\n id?: string\n name?: string\n required?: boolean\n 'aria-label'?: string\n 'aria-describedby'?: string\n}\n\nfunction extractTime(date: Date): string {\n return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`\n}\n\nfunction applyTimeToDate(base: Date, time: string): Date {\n const [hourStr = '0', minuteStr = '0'] = time.split(':')\n const hour = parseInt(hourStr, 10)\n const minute = parseInt(minuteStr, 10)\n const next = new Date(base)\n next.setHours(isNaN(hour) ? 0 : hour, isNaN(minute) ? 0 : minute, 0, 0)\n return next\n}\n\nfunction startOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(0, 0, 0, 0)\n return next\n}\n\nexport function DatePicker({\n value,\n onChange,\n placeholder,\n size = 'default',\n disabled = false,\n readOnly = false,\n footer = 'apply-cancel',\n closeOnSelect,\n showTodayButton = true,\n showClearButton = true,\n withTime = false,\n minuteStep = 1,\n align = 'start',\n minDate,\n maxDate,\n locale,\n displayFormat,\n className,\n popoverClassName,\n id,\n name,\n required,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n}: DatePickerProps) {\n const resolvedCloseOnSelect = closeOnSelect ?? footer === 'today-clear'\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [draft, setDraft] = React.useState<Date | null>(value ?? null)\n\n const isInteractive = !disabled && !readOnly\n const useDraft = footer === 'apply-cancel'\n\n React.useEffect(() => {\n if (open) setDraft(value ?? null)\n }, [open, value])\n\n const resolvedFormat = withTime\n ? resolvePublicDateTimeFormat(locale, displayFormat)\n : resolvePublicDateFormat(locale, displayFormat)\n const placeholderText = placeholder ?? (\n withTime\n ? t('ui.dateTimePicker.placeholder', 'Pick date and time')\n : t('ui.datePicker.placeholder', 'Pick a date')\n )\n\n const formattedValue = React.useMemo(() => {\n if (!value) return null\n return formatWithPublicDateFormat(value, resolvedFormat, locale)\n }, [value, resolvedFormat, locale])\n\n const handleDaySelect = React.useCallback(\n (day: Date | undefined) => {\n if (!day || !isInteractive) return\n const reference = withTime ? (useDraft ? draft : value) : null\n const base = withTime\n ? applyTimeToDate(day, reference ? extractTime(reference) : '00:00')\n : startOfDay(day)\n if (useDraft) {\n setDraft(base)\n } else {\n onChange(base)\n if (footer === 'none' || resolvedCloseOnSelect) setOpen(false)\n }\n },\n [isInteractive, withTime, useDraft, draft, value, footer, resolvedCloseOnSelect, onChange],\n )\n\n const handleTimeChange = React.useCallback(\n (time: string) => {\n const target = useDraft ? draft : value\n if (!target) return\n const next = applyTimeToDate(target, time)\n if (useDraft) setDraft(next)\n else onChange(next)\n },\n [useDraft, draft, value, onChange],\n )\n\n const handleApply = React.useCallback(() => {\n onChange(draft)\n setOpen(false)\n }, [draft, onChange])\n\n const handleCancel = React.useCallback(() => {\n setDraft(value ?? null)\n setOpen(false)\n }, [value])\n\n const handleToday = React.useCallback(() => {\n const today = withTime ? new Date() : startOfDay(new Date())\n onChange(today)\n setOpen(false)\n }, [withTime, onChange])\n\n const handleClear = React.useCallback(() => {\n onChange(null)\n setOpen(false)\n }, [onChange])\n\n const disabledMatcher = React.useMemo(() => {\n if (!minDate && !maxDate) return undefined\n const matchers: import('react-day-picker').Matcher[] = []\n if (minDate) matchers.push({ before: minDate })\n if (maxDate) matchers.push({ after: maxDate })\n return matchers\n }, [minDate, maxDate])\n\n const heightClass = size === 'sm' ? 'h-8 px-2.5 text-xs' : 'h-9 px-3 text-sm'\n const selectedDate = useDraft ? draft : value\n\n return (\n <Popover open={open} onOpenChange={isInteractive ? setOpen : undefined}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n id={id}\n name={name}\n aria-label={ariaLabel}\n aria-describedby={ariaDescribedBy}\n aria-haspopup=\"dialog\"\n aria-required={required ? true : undefined}\n data-crud-focus-target=\"\"\n data-slot=\"date-picker-trigger\"\n disabled={disabled}\n className={cn(\n 'w-full inline-flex items-center gap-2 rounded-md border border-input bg-background shadow-xs transition-colors text-left',\n 'focus-visible:outline-none focus-visible:shadow-focus focus-visible:border-foreground',\n 'hover:bg-muted/40',\n 'disabled:bg-bg-disabled disabled:border-border-disabled disabled:shadow-none disabled:hover:bg-bg-disabled disabled:cursor-not-allowed',\n 'aria-invalid:border-destructive',\n heightClass,\n readOnly && 'cursor-default opacity-70',\n !formattedValue && 'text-muted-foreground',\n className,\n )}\n onClick={isInteractive ? undefined : (event) => event.preventDefault()}\n >\n <CalendarIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <span className=\"flex-1 truncate\">\n {formattedValue ?? placeholderText}\n </span>\n </button>\n </PopoverTrigger>\n <PopoverContent align={align} className={cn('w-auto p-0', popoverClassName)}>\n <Calendar\n mode=\"single\"\n selected={selectedDate ?? undefined}\n onSelect={handleDaySelect}\n locale={locale}\n disabled={disabledMatcher}\n initialFocus\n />\n {withTime && (\n <div className=\"flex items-center gap-2 border-t px-3 py-2\">\n <span className=\"text-sm text-muted-foreground shrink-0\">\n {t('ui.datePicker.timeLabel', 'Time')}:\n </span>\n <TimeInput\n value={selectedDate ? extractTime(selectedDate) : undefined}\n onChange={handleTimeChange}\n minuteStep={minuteStep}\n disabled={!isInteractive || !selectedDate}\n />\n </div>\n )}\n {footer === 'apply-cancel' && (\n <div className=\"flex items-center justify-end gap-2 border-t px-3 py-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleCancel}>\n {t('ui.datePicker.cancelButton', 'Cancel')}\n </Button>\n <Button type=\"button\" size=\"sm\" onClick={handleApply}>\n {t('ui.datePicker.applyButton', 'Apply')}\n </Button>\n </div>\n )}\n {footer === 'today-clear' && (showTodayButton || showClearButton) && (\n <div className=\"flex items-center justify-between gap-2 border-t px-3 py-2\">\n {showTodayButton ? (\n <LinkButton type=\"button\" variant=\"primary\" onClick={handleToday}>\n {t('ui.datePicker.todayButton', 'Today')}\n </LinkButton>\n ) : null}\n {showClearButton ? (\n <LinkButton type=\"button\" variant=\"gray\" onClick={handleClear} className=\"ml-auto\">\n {t('ui.datePicker.clearButton', 'Clear')}\n </LinkButton>\n ) : null}\n </div>\n )}\n </PopoverContent>\n </Popover>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { format } from "date-fns";
|
|
5
4
|
import { CalendarIcon } from "lucide-react";
|
|
6
5
|
import { cn } from "@open-mercato/shared/lib/utils";
|
|
7
6
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
@@ -11,28 +10,10 @@ import { Button } from "./button.js";
|
|
|
11
10
|
import {
|
|
12
11
|
defaultDateRangePresets
|
|
13
12
|
} from "./date-picker-helpers.js";
|
|
14
|
-
|
|
15
|
-
"pl",
|
|
16
|
-
"de",
|
|
17
|
-
"fr",
|
|
18
|
-
"es",
|
|
19
|
-
"it",
|
|
20
|
-
"pt",
|
|
21
|
-
"nl",
|
|
22
|
-
"ru",
|
|
23
|
-
"cs",
|
|
24
|
-
"sk",
|
|
25
|
-
"hu",
|
|
26
|
-
"ro"
|
|
27
|
-
]);
|
|
28
|
-
function deriveDateFormat(locale) {
|
|
29
|
-
const code = locale?.code?.split("-")[0]?.toLowerCase() ?? "";
|
|
30
|
-
return code && DAY_FIRST_LOCALE_CODES.has(code) ? "d MMM yyyy" : "MMM d, yyyy";
|
|
31
|
-
}
|
|
13
|
+
import { formatWithPublicDateFormat, resolvePublicDateFormat } from "./date-format.js";
|
|
32
14
|
function defaultFormatRange(range, locale) {
|
|
33
|
-
const fmt =
|
|
34
|
-
|
|
35
|
-
return `${format(range.start, fmt, opts)} \u2013 ${format(range.end, fmt, opts)}`;
|
|
15
|
+
const fmt = resolvePublicDateFormat(locale);
|
|
16
|
+
return `${formatWithPublicDateFormat(range.start, fmt, locale) ?? ""} \u2013 ${formatWithPublicDateFormat(range.end, fmt, locale) ?? ""}`;
|
|
36
17
|
}
|
|
37
18
|
function toRDPRange(range) {
|
|
38
19
|
if (!range) return void 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/primitives/date-range-picker.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { Locale } from 'date-fns'\nimport { CalendarIcon } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Popover, PopoverContent, PopoverTrigger } from './popover'\nimport { Calendar } from './calendar'\nimport { Button } from './button'\nimport type { DateRange as RDPRange } from 'react-day-picker'\nimport type { DateRange } from '../backend/date-range/dateRanges'\nimport {\n defaultDateRangePresets,\n type DateRangePresetItem,\n} from './date-picker-helpers'\nimport { formatWithPublicDateFormat, resolvePublicDateFormat } from './date-format'\n\nexport type { DateRangePresetItem } from './date-picker-helpers'\n\nexport type DateRangePickerProps = {\n value?: DateRange | null\n onChange: (value: DateRange | null) => void\n presets?: DateRangePresetItem[]\n showPresets?: boolean\n placeholder?: string\n size?: 'sm' | 'default'\n disabled?: boolean\n readOnly?: boolean\n withFooter?: boolean\n align?: 'start' | 'center' | 'end'\n minDate?: Date\n maxDate?: Date\n numberOfMonths?: 1 | 2\n locale?: Locale\n formatRange?: (value: DateRange, locale?: Locale) => string\n className?: string\n popoverClassName?: string\n id?: string\n name?: string\n required?: boolean\n 'aria-label'?: string\n 'aria-describedby'?: string\n}\n\nfunction defaultFormatRange(range: DateRange, locale?: Locale): string {\n const fmt = resolvePublicDateFormat(locale)\n return `${formatWithPublicDateFormat(range.start, fmt, locale) ?? ''} \u2013 ${formatWithPublicDateFormat(range.end, fmt, locale) ?? ''}`\n}\n\nfunction toRDPRange(range: DateRange | null | undefined): RDPRange | undefined {\n if (!range) return undefined\n return { from: range.start, to: range.end }\n}\n\nfunction fromRDPRange(rdp: RDPRange | undefined): DateRange | null {\n if (!rdp || !rdp.from) return null\n return { start: rdp.from, end: rdp.to ?? rdp.from }\n}\n\nfunction rangesEqual(a: DateRange | null, b: DateRange | null): boolean {\n if (!a && !b) return true\n if (!a || !b) return false\n return a.start.getTime() === b.start.getTime() && a.end.getTime() === b.end.getTime()\n}\n\nexport function DateRangePicker({\n value,\n onChange,\n presets,\n showPresets = true,\n placeholder,\n size = 'default',\n disabled = false,\n readOnly = false,\n withFooter = true,\n align = 'start',\n minDate,\n maxDate,\n numberOfMonths = 2,\n locale,\n formatRange,\n className,\n popoverClassName,\n id,\n name,\n required,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n}: DateRangePickerProps) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [draft, setDraft] = React.useState<DateRange | null>(value ?? null)\n const [activePresetId, setActivePresetId] = React.useState<string | null>(null)\n\n const isInteractive = !disabled && !readOnly\n const useDraft = withFooter\n\n React.useEffect(() => {\n if (open) {\n setDraft(value ?? null)\n setActivePresetId(null)\n }\n }, [open, value])\n\n const resolvedPresets = React.useMemo<DateRangePresetItem[]>(\n () => presets ?? defaultDateRangePresets(),\n [presets],\n )\n\n const placeholderText = placeholder ?? t('ui.dateRangePicker.placeholder', 'Pick a date range')\n const formatter = formatRange ?? defaultFormatRange\n const formattedValue = React.useMemo(() => {\n if (!value) return null\n return formatter(value, locale)\n }, [value, formatter, locale])\n\n const selectedDate = useDraft ? draft : value\n const rdpSelected = toRDPRange(selectedDate)\n\n const handleRangeSelect = React.useCallback(\n (rdpRange: RDPRange | undefined) => {\n if (!isInteractive) return\n const next = fromRDPRange(rdpRange)\n setActivePresetId(null)\n if (useDraft) {\n setDraft(next)\n } else {\n onChange(next)\n if (next?.start && next?.end) setOpen(false)\n }\n },\n [isInteractive, useDraft, onChange],\n )\n\n const handlePresetClick = React.useCallback(\n (preset: DateRangePresetItem) => {\n if (!isInteractive) return\n const range = preset.range()\n setActivePresetId(preset.id)\n if (useDraft) {\n setDraft(range)\n } else {\n onChange(range)\n setOpen(false)\n }\n },\n [isInteractive, useDraft, onChange],\n )\n\n const handleApply = React.useCallback(() => {\n onChange(draft)\n setOpen(false)\n }, [draft, onChange])\n\n const handleCancel = React.useCallback(() => {\n setDraft(value ?? null)\n setActivePresetId(null)\n setOpen(false)\n }, [value])\n\n const disabledMatcher = React.useMemo(() => {\n if (!minDate && !maxDate) return undefined\n const matchers: import('react-day-picker').Matcher[] = []\n if (minDate) matchers.push({ before: minDate })\n if (maxDate) matchers.push({ after: maxDate })\n return matchers\n }, [minDate, maxDate])\n\n const heightClass = size === 'sm' ? 'h-8 px-2.5 text-xs' : 'h-9 px-3 text-sm'\n\n return (\n <Popover open={open} onOpenChange={isInteractive ? setOpen : undefined}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n id={id}\n name={name}\n aria-label={ariaLabel}\n aria-describedby={ariaDescribedBy}\n aria-haspopup=\"dialog\"\n aria-required={required ? true : undefined}\n data-crud-focus-target=\"\"\n data-slot=\"date-range-picker-trigger\"\n disabled={disabled}\n className={cn(\n 'w-full inline-flex items-center gap-2 rounded-md border border-input bg-background shadow-xs transition-colors text-left',\n 'focus-visible:outline-none focus-visible:shadow-focus focus-visible:border-foreground',\n 'hover:bg-muted/40',\n 'disabled:bg-bg-disabled disabled:border-border-disabled disabled:shadow-none disabled:hover:bg-bg-disabled disabled:cursor-not-allowed',\n 'aria-invalid:border-destructive',\n heightClass,\n readOnly && 'cursor-default opacity-70',\n !formattedValue && 'text-muted-foreground',\n className,\n )}\n onClick={isInteractive ? undefined : (event) => event.preventDefault()}\n >\n <CalendarIcon className=\"h-4 w-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n <span className=\"flex-1 truncate\">\n {formattedValue ?? placeholderText}\n </span>\n </button>\n </PopoverTrigger>\n <PopoverContent\n align={align}\n className={cn(\n 'w-auto p-0 flex flex-col overflow-hidden',\n 'max-h-[var(--radix-popover-content-available-height)]',\n popoverClassName,\n )}\n >\n <div className=\"flex min-h-0 flex-1 overflow-auto\">\n {showPresets ? (\n <div\n data-slot=\"date-range-presets\"\n className=\"flex w-[200px] shrink-0 flex-col gap-0.5 overflow-y-auto border-r p-2\"\n >\n {resolvedPresets.map((preset) => {\n const isActive = activePresetId === preset.id\n return (\n <button\n key={preset.id}\n type=\"button\"\n onClick={() => handlePresetClick(preset)}\n className={cn(\n 'inline-flex h-8 items-center rounded-md px-3 text-left text-sm transition-colors shrink-0',\n 'hover:bg-accent hover:text-accent-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n isActive && 'bg-muted font-medium text-foreground',\n )}\n >\n {t(preset.labelKey, preset.id.replace(/_/g, ' '))}\n </button>\n )\n })}\n </div>\n ) : null}\n <div className=\"p-3\">\n <Calendar\n mode=\"range\"\n selected={rdpSelected}\n onSelect={handleRangeSelect}\n locale={locale}\n disabled={disabledMatcher}\n numberOfMonths={numberOfMonths}\n initialFocus\n />\n </div>\n </div>\n {withFooter ? (\n <div className=\"flex shrink-0 items-center justify-between gap-3 border-t bg-popover px-3 py-2\">\n <span className=\"truncate text-xs text-muted-foreground\">\n {selectedDate\n ? `${t('ui.dateRangePicker.rangeLabel', 'Range')}: ${formatter(selectedDate, locale)}`\n : t('ui.dateRangePicker.rangeEmpty', 'No range selected')}\n </span>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleCancel}>\n {t('ui.dateRangePicker.cancelButton', 'Cancel')}\n </Button>\n <Button type=\"button\" size=\"sm\" onClick={handleApply}>\n {t('ui.dateRangePicker.applyButton', 'Apply')}\n </Button>\n </div>\n </div>\n ) : null}\n </PopoverContent>\n </Popover>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.6.2-develop.
|
|
3
|
+
"version": "0.6.2-develop.3446.1.bd060c6017",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"@radix-ui/react-select": "^2.1.6",
|
|
134
134
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
135
135
|
"@tanstack/react-virtual": "^3.13.24",
|
|
136
|
-
"date-fns": "
|
|
136
|
+
"date-fns": "4.1.0",
|
|
137
137
|
"react-big-calendar": "^1.19.4",
|
|
138
138
|
"react-day-picker": "^9.14.0",
|
|
139
139
|
"react-markdown": "^10.1.0",
|
|
@@ -141,13 +141,13 @@
|
|
|
141
141
|
"remark-gfm": "^4.0.1"
|
|
142
142
|
},
|
|
143
143
|
"peerDependencies": {
|
|
144
|
-
"@open-mercato/shared": "0.6.2-develop.
|
|
144
|
+
"@open-mercato/shared": "0.6.2-develop.3446.1.bd060c6017",
|
|
145
145
|
"react": ">=18.0.0",
|
|
146
146
|
"react-dom": ">=18.0.0",
|
|
147
147
|
"react-is": ">=18.0.0"
|
|
148
148
|
},
|
|
149
149
|
"devDependencies": {
|
|
150
|
-
"@open-mercato/shared": "0.6.2-develop.
|
|
150
|
+
"@open-mercato/shared": "0.6.2-develop.3446.1.bd060c6017",
|
|
151
151
|
"@testing-library/dom": "^10.4.1",
|
|
152
152
|
"@testing-library/jest-dom": "^6.9.1",
|
|
153
153
|
"@testing-library/react": "^16.3.1",
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
import * as React from 'react'
|
|
25
25
|
import {
|
|
26
26
|
AlertTriangle,
|
|
27
|
-
|
|
27
|
+
BadgeQuestionMark,
|
|
28
|
+
Database,
|
|
28
29
|
ExternalLink,
|
|
29
|
-
|
|
30
|
+
FileQuestion,
|
|
30
31
|
Lightbulb,
|
|
31
32
|
Loader2,
|
|
32
33
|
PanelRightOpen,
|
|
@@ -407,24 +408,24 @@ export function AiAssistantLauncher({
|
|
|
407
408
|
{
|
|
408
409
|
label: t('ai_assistant.launcher.welcome.suggestion1', 'What can you help me with?'),
|
|
409
410
|
prompt: 'What can you help me with on this tenant?',
|
|
410
|
-
icon: <
|
|
411
|
+
icon: <BadgeQuestionMark className="size-4 text-foreground" />,
|
|
411
412
|
},
|
|
412
413
|
{
|
|
413
414
|
label: t('ai_assistant.launcher.welcome.suggestion2', 'Show what data you can access'),
|
|
414
415
|
prompt: 'Describe the data you can read for this tenant — entities, fields, and limits.',
|
|
415
|
-
icon: <
|
|
416
|
+
icon: <Database className="size-4 text-foreground" />,
|
|
416
417
|
},
|
|
417
418
|
{
|
|
418
419
|
label: t('ai_assistant.launcher.welcome.suggestion3', 'Suggest things to try'),
|
|
419
420
|
prompt:
|
|
420
421
|
'Suggest five concrete questions I could ask you that would surface useful insights for this tenant.',
|
|
421
|
-
icon: <Lightbulb className="size-4" />,
|
|
422
|
+
icon: <Lightbulb className="size-4 text-foreground" />,
|
|
422
423
|
},
|
|
423
424
|
{
|
|
424
425
|
label: t('ai_assistant.launcher.welcome.suggestion4', 'How do I use this assistant?'),
|
|
425
426
|
prompt:
|
|
426
427
|
'Walk me through how to use this assistant: when to ask, what tools you call, and how confirmations work.',
|
|
427
|
-
icon: <
|
|
428
|
+
icon: <FileQuestion className="size-4 text-foreground" />,
|
|
428
429
|
},
|
|
429
430
|
]
|
|
430
431
|
return [...(activeAgent?.suggestions ?? []), ...generic]
|
|
@@ -462,12 +463,12 @@ export function AiAssistantLauncher({
|
|
|
462
463
|
variant="ghost"
|
|
463
464
|
size="sm"
|
|
464
465
|
onClick={openPicker}
|
|
465
|
-
className={cn('hidden sm:inline-flex items-center gap-2', className)}
|
|
466
|
+
className={cn('hidden sm:inline-flex items-center gap-2 text-foreground [&_svg]:text-foreground', className)}
|
|
466
467
|
data-ai-launcher-trigger=""
|
|
467
468
|
aria-label={triggerLabel}
|
|
468
469
|
title={triggerLabel}
|
|
469
470
|
>
|
|
470
|
-
<AiIcon className="size-4" />
|
|
471
|
+
<AiIcon className="size-4 text-foreground" />
|
|
471
472
|
<span>{shortLabel}</span>
|
|
472
473
|
<span className="ml-2 rounded border px-1 text-xs text-muted-foreground">
|
|
473
474
|
⌘L
|
|
@@ -478,12 +479,12 @@ export function AiAssistantLauncher({
|
|
|
478
479
|
type="button"
|
|
479
480
|
variant="ghost"
|
|
480
481
|
size="sm"
|
|
481
|
-
className="sm:hidden"
|
|
482
|
+
className="sm:hidden text-foreground [&_svg]:text-foreground"
|
|
482
483
|
onClick={openPicker}
|
|
483
484
|
aria-label={triggerLabel}
|
|
484
485
|
data-ai-launcher-trigger-mobile=""
|
|
485
486
|
>
|
|
486
|
-
<AiIcon className="size-4" />
|
|
487
|
+
<AiIcon className="size-4 text-foreground" />
|
|
487
488
|
</IconButton>
|
|
488
489
|
<Dialog open={pickerOpen} onOpenChange={setPickerOpen}>
|
|
489
490
|
<DialogContent
|