@powerportalspro/react-fluent 5.1.0 → 6.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/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { makeStyles, mergeClasses, Input, tokens, Toolbar, ToolbarButton, ToolbarDivider, Menu, MenuTrigger, MenuPopover, MenuList, MenuItemRadio, Button, createLightTheme, createDarkTheme, makeStaticStyles, MessageBar, MessageBarBody, Dropdown, Option, Spinner, Field, Textarea, Switch, Checkbox, Label, TabList, Tab, Tooltip, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, RadioGroup, Radio, useId as useId$1, Text, Overflow, OverflowItem, SearchBox, CounterBadge, Popover, PopoverTrigger, PopoverSurface, MenuItem, Combobox, TagPicker, TagPickerControl, TagPickerGroup, Tag, TagPickerInput, TagPickerList, TagPickerOption, webDarkTheme, webLightTheme, teamsDarkTheme, teamsLightTheme, FluentProvider, Avatar, Divider, OverlayDrawer, DrawerHeader, DrawerHeaderTitle, DrawerBody, useOverflowMenu, useIsOverflowItemVisible, Title3, Table, TableHeader, TableRow, TableHeaderCell, TableBody, TableCell, MessageBarTitle, DialogTrigger } from '@fluentui/react-components';
2
- import { MaskMode, useMaskedTextField, useT, usePowerPortalsPro, useRecordContext, useColumnMetadata, useLocale, MainContext, useTableMetadata, useWarnAboutUnsavedChanges, useMainContext, useViewsForTable, useViewMetadata, QueryStatus, useGridData, RecordContext, DialogServiceProvider, useUrlCultureStrategy, useDefaultLocale, useSupportedLocales, setAspNetCultureCookie, buildLocalePath, useAuth, AuthStatus, useOverlayServiceState, OverlayServiceProvider, useOverlayForTarget, useLocalization, useLocalizationLoaded, useValidationContext, useDialogService, useRunWithOverlay, useRecordContextOptional, useOrganizationSettings, useUnsavedChangesGuard, useColumnValidationErrors } from '@powerportalspro/react';
1
+ import { makeStyles, mergeClasses, Input, tokens, Toolbar, ToolbarButton, ToolbarDivider, Menu, MenuTrigger, MenuPopover, MenuList, MenuItemRadio, Button, createLightTheme, createDarkTheme, makeStaticStyles, MessageBar, MessageBarBody, Dropdown, Option, Spinner, Field, Textarea, Switch, Checkbox, Label, TabList, Tab, Tooltip, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, RadioGroup, Radio, useId as useId$1, Text, Overflow, OverflowItem, SearchBox, CounterBadge, Popover, PopoverTrigger, PopoverSurface, MenuItem, Combobox, TagPicker, TagPickerControl, TagPickerGroup, Tag, TagPickerInput, TagPickerList, TagPickerOption, webDarkTheme, webLightTheme, teamsDarkTheme, teamsLightTheme, FluentProvider, Avatar, Divider, OverlayDrawer, DrawerHeader, DrawerHeaderTitle, DrawerBody, useOverflowMenu, useIsOverflowItemVisible, Title3, Table, TableHeader, TableRow, TableHeaderCell, TableBody, TableCell, createTableColumn, DataGrid, DataGridHeader, DataGridRow, DataGridHeaderCell, DataGridBody, DataGridCell, MessageBarTitle, DialogTrigger } from '@fluentui/react-components';
2
+ import { MaskMode, useMaskedTextField, useT, usePowerPortalsPro, useRecordContext, useColumnMetadata, useLocale, MainContext, useTableMetadata, useWarnAboutUnsavedChanges, useMainContext, useViewsForTable, useViewMetadata, QueryStatus, useGridData, RecordContext, DialogServiceProvider, useUrlCultureStrategy, useDefaultLocale, useSupportedLocales, setAspNetCultureCookie, buildLocalePath, useAuth, AuthStatus, useOverlayServiceState, OverlayServiceProvider, useOverlayForTarget, useLocalization, useLocalizationLoaded, useValidationContext, useDialogService, useRunWithOverlay, useRecordContextOptional, useOrganizationSettings, useUnsavedChangesGuard, useColumnValidationErrors, isKeyModeEnabled } from '@powerportalspro/react';
3
3
  import { forwardRef, useRef, useImperativeHandle, useState, useEffect, useCallback, createContext, useMemo, useId, useLayoutEffect, useContext, useTransition, Children, isValidElement } from 'react';
4
4
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
- import { ArrowRotateCounterclockwise20Regular, ArrowRotateClockwise20Regular, FlipHorizontal20Regular, FlipVertical20Regular, ZoomIn20Regular, ZoomOut20Regular, ArrowReset20Regular, ChevronDown16Regular, Grid20Regular, Crop20Regular, FullScreenMinimize20Regular, FullScreenMaximize20Regular, ArrowDownload20Regular, Prohibited20Regular, Checkmark20Filled, ArrowDownload24Regular, ArrowClockwise24Regular, ChevronUp12Regular, ChevronDown12Regular, DocumentArrowLeft24Regular, ChevronDown20Regular, ChevronRight20Regular, Folder20Regular, Document20Regular, ErrorCircle20Regular, Eye20Regular, Dismiss20Regular, Edit20Regular, Add16Regular, Edit16Regular, LinkAdd16Regular, LinkDismiss16Regular, Delete16Regular, Settings20Regular, Search20Regular, ArrowClockwise20Regular, ArrowSortDown16Regular, ArrowSortUp16Regular, Dismiss24Regular, LinkAdd20Regular, QuestionCircle24Regular, Info24Regular, DismissCircle24Regular, Warning24Regular, CheckmarkCircle24Regular, ArrowExit20Regular, PersonEdit20Regular, ArrowSwap20Regular, SignOut20Regular, MoreVerticalRegular, ArrowSyncRegular, ArrowDownloadRegular, Save24Regular, Delete24Regular, Add20Regular, Delete20Regular, LinkDismiss20Regular, ArrowUpload32Regular, ArrowUpload24Regular, ArrowSync24Regular, DrawerArrowDownload24Regular, Money20Regular, ErrorCircle12Filled, DocumentText20Regular, FolderZip20Regular, MusicNote220Regular, Video20Regular, Image20Regular, SlideText20Regular, DocumentTable20Regular, DocumentPdf20Regular, MoreHorizontal20Regular, ChevronDoubleLeft20Regular, ChevronLeft20Regular, ChevronDoubleRight20Regular, Info12Regular, Checkmark16Regular, Dismiss16Regular } from '@fluentui/react-icons';
5
+ import { ArrowRotateCounterclockwise20Regular, ArrowRotateClockwise20Regular, FlipHorizontal20Regular, FlipVertical20Regular, ZoomIn20Regular, ZoomOut20Regular, ArrowReset20Regular, ChevronDown16Regular, Grid20Regular, Crop20Regular, FullScreenMinimize20Regular, FullScreenMaximize20Regular, ArrowDownload20Regular, Prohibited20Regular, Checkmark20Filled, ArrowDownload24Regular, ArrowClockwise24Regular, ChevronUp12Regular, ChevronDown12Regular, DocumentArrowLeft24Regular, ChevronDown20Regular, ChevronRight20Regular, Folder20Regular, Document20Regular, ErrorCircle20Regular, Eye20Regular, Dismiss20Regular, Edit20Regular, Add16Regular, Edit16Regular, LinkAdd16Regular, LinkDismiss16Regular, Delete16Regular, Settings20Regular, Search20Regular, ArrowClockwise20Regular, ArrowSortDown16Regular, ArrowSortUp16Regular, Dismiss24Regular, LinkAdd20Regular, QuestionCircle24Regular, Info24Regular, DismissCircle24Regular, Warning24Regular, CheckmarkCircle24Regular, ArrowExit20Regular, PersonEdit20Regular, ArrowSwap20Regular, SignOut20Regular, MoreVerticalRegular, ArrowSyncRegular, EyeRegular, ArrowDownloadRegular, SearchRegular, DismissRegular, Save24Regular, Delete24Regular, Add20Regular, Delete20Regular, LinkDismiss20Regular, ArrowUpload32Regular, ArrowUpload24Regular, ArrowSync24Regular, DrawerArrowDownload24Regular, Money20Regular, ErrorCircle12Filled, DocumentText20Regular, FolderZip20Regular, MusicNote220Regular, Video20Regular, Image20Regular, SlideText20Regular, DocumentTable20Regular, DocumentPdf20Regular, MoreHorizontal20Regular, ChevronDoubleLeft20Regular, ChevronLeft20Regular, ChevronDoubleRight20Regular, Info12Regular, Checkmark16Regular, Dismiss16Regular } from '@fluentui/react-icons';
6
6
  import { RequiredLevel, DateTimeBehavior, getRecordReferenceId, RelationshipFilterMode, SwitchIdentityResult, TableSecurityPermission } from '@powerportalspro/core';
7
7
  import { DatePicker } from '@fluentui/react-datepicker-compat';
8
8
  import { TimePicker } from '@fluentui/react-timepicker-compat';
@@ -9015,7 +9015,13 @@ function MainGridImpl({
9015
9015
  (row) => row.properties?.[columnName],
9016
9016
  {
9017
9017
  id: columnName,
9018
- header: () => resolvedColumn.displayName,
9018
+ // Headers normally show the server-resolved Dataverse display name, which bypasses the
9019
+ // client `t()` — so the ?ppp-show-keys aid couldn't reach them. Consult key-mode here too:
9020
+ // when it's on, show the column's localization key (the one a consumer would override),
9021
+ // keyed by the column's OWN owning table (`resolvedColumn.tableName` — the related table
9022
+ // for a linked-entity column, not the grid's primary table). For linked columns the name
9023
+ // is a dotted alias path, so take the segment after the last dot as the real column name.
9024
+ header: () => isKeyModeEnabled() ? `tables.${resolvedColumn.tableName}.columns.${columnName.split(".").pop() ?? columnName}.label` : resolvedColumn.displayName,
9019
9025
  cell: (cellCtx) => {
9020
9026
  const rowRecord = cellCtx.row.original;
9021
9027
  const rowId = rowRecord.id;
@@ -13558,27 +13564,57 @@ function NavMenu({ children, width, className, style }) {
13558
13564
  useNavSelectedStyles();
13559
13565
  const location = useLocation();
13560
13566
  const navigate = useNavigate();
13561
- const { allRoutes, defaultOpen } = useMemo(
13567
+ const { allRoutes, groups, defaultOpenIndexes } = useMemo(
13562
13568
  () => collectNavTree(children),
13563
13569
  [children]
13564
13570
  );
13565
- const selectedValue = useMemo(
13571
+ const selectedRoute = useMemo(
13566
13572
  () => bestRouteMatch(allRoutes, location.pathname),
13567
13573
  [allRoutes, location.pathname]
13568
13574
  );
13575
+ const [openIndexes, setOpenIndexes] = useState(
13576
+ () => /* @__PURE__ */ new Set([
13577
+ ...defaultOpenIndexes,
13578
+ // Seed the active route's group too, so it's open on first paint
13579
+ // (the effect below only fires after mount).
13580
+ ...selectedRoute && selectedRoute.groupIndex >= 0 ? [selectedRoute.groupIndex] : []
13581
+ ])
13582
+ );
13583
+ const selectedGroupIndex = selectedRoute?.groupIndex ?? -1;
13584
+ useEffect(() => {
13585
+ if (selectedGroupIndex < 0) return;
13586
+ setOpenIndexes(
13587
+ (prev) => prev.has(selectedGroupIndex) ? prev : new Set(prev).add(selectedGroupIndex)
13588
+ );
13589
+ }, [selectedGroupIndex]);
13590
+ const openCategories = useMemo(
13591
+ () => groups.filter((_, index) => openIndexes.has(index)),
13592
+ [groups, openIndexes]
13593
+ );
13569
13594
  const handleSelect = (_, data) => {
13570
13595
  const value = data.value;
13571
13596
  if (typeof value === "string" && value.startsWith("/")) {
13572
13597
  navigate(value);
13573
13598
  }
13574
13599
  };
13600
+ const handleCategoryToggle = (_, data) => {
13601
+ const index = groups.indexOf(data.value);
13602
+ if (index < 0) return;
13603
+ setOpenIndexes((prev) => {
13604
+ const next = new Set(prev);
13605
+ if (next.has(index)) next.delete(index);
13606
+ else next.add(index);
13607
+ return next;
13608
+ });
13609
+ };
13575
13610
  const navStyle = width ? { ...style, minWidth: width, maxWidth: width } : style;
13576
13611
  return /* @__PURE__ */ jsx(
13577
13612
  Nav,
13578
13613
  {
13579
- selectedValue: selectedValue ?? "",
13614
+ selectedValue: selectedRoute?.to ?? "",
13580
13615
  onNavItemSelect: handleSelect,
13581
- defaultOpenCategories: defaultOpen,
13616
+ openCategories,
13617
+ onNavCategoryItemToggle: handleCategoryToggle,
13582
13618
  className: mergeClasses("ppp-nav-menu", styles.nav, className),
13583
13619
  style: navStyle,
13584
13620
  children
@@ -13608,42 +13644,46 @@ function NavItem({ to, icon, children }) {
13608
13644
  }
13609
13645
  function collectNavTree(children) {
13610
13646
  const allRoutes = [];
13611
- const defaultOpen = [];
13612
- const visit = (nodes) => {
13647
+ const groups = [];
13648
+ const defaultOpenIndexes = [];
13649
+ const visit = (nodes, groupIndex) => {
13613
13650
  Children.forEach(nodes, (child) => {
13614
13651
  if (!isValidElement(child)) return;
13615
13652
  if (child.type === NavGroup) {
13616
13653
  const props = child.props;
13617
13654
  const value = props.value ?? `group:${props.label}`;
13618
- if (props.defaultExpanded) defaultOpen.push(value);
13619
- visit(props.children);
13655
+ const index = groups.length;
13656
+ groups.push(value);
13657
+ if (props.defaultExpanded) defaultOpenIndexes.push(index);
13658
+ visit(props.children, index);
13620
13659
  return;
13621
13660
  }
13622
13661
  if (child.type === NavItem) {
13623
13662
  const props = child.props;
13624
13663
  allRoutes.push({
13625
13664
  to: props.to,
13626
- match: props.match ?? (props.to === "/" ? "exact" : "prefix")
13665
+ match: props.match ?? (props.to === "/" ? "exact" : "prefix"),
13666
+ groupIndex
13627
13667
  });
13628
13668
  return;
13629
13669
  }
13630
13670
  const wrapperChildren = child.props.children;
13631
- if (wrapperChildren !== void 0) visit(wrapperChildren);
13671
+ if (wrapperChildren !== void 0) visit(wrapperChildren, groupIndex);
13632
13672
  });
13633
13673
  };
13634
- visit(children);
13635
- return { allRoutes, defaultOpen };
13674
+ visit(children, -1);
13675
+ return { allRoutes, groups, defaultOpenIndexes };
13636
13676
  }
13637
13677
  function bestRouteMatch(routes, pathname) {
13638
13678
  const normalize = (p) => p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
13639
13679
  const target = normalize(pathname);
13640
13680
  let best;
13641
13681
  let bestLen = -1;
13642
- for (const { to, match } of routes) {
13643
- const route = normalize(to);
13644
- const matches = route === target || match === "prefix" && (route === "/" || target.startsWith(route + "/"));
13682
+ for (const candidate of routes) {
13683
+ const route = normalize(candidate.to);
13684
+ const matches = route === target || candidate.match === "prefix" && (route === "/" || target.startsWith(route + "/"));
13645
13685
  if (matches && route.length > bestLen) {
13646
- best = to;
13686
+ best = candidate;
13647
13687
  bestLen = route.length;
13648
13688
  }
13649
13689
  }
@@ -14386,8 +14426,28 @@ var useStyles12 = makeStyles({
14386
14426
  mergedButtons: {
14387
14427
  display: "flex",
14388
14428
  flexWrap: "wrap",
14389
- gap: tokens.spacingHorizontalS
14429
+ gap: tokens.spacingHorizontalL
14430
+ },
14431
+ // One culture group: language label + inspect button + download button, kept together.
14432
+ mergedCulture: {
14433
+ display: "flex",
14434
+ alignItems: "center",
14435
+ gap: tokens.spacingHorizontalXS
14390
14436
  },
14437
+ mergedCultureLabel: { fontSize: tokens.fontSizeBase200 },
14438
+ dialogSurface: { maxWidth: "1100px", width: "90vw" },
14439
+ dialogToolbar: {
14440
+ display: "flex",
14441
+ alignItems: "center",
14442
+ gap: tokens.spacingHorizontalM,
14443
+ flexWrap: "wrap",
14444
+ marginBottom: tokens.spacingVerticalS
14445
+ },
14446
+ // Scroll viewport for the merged grid so a tall result set scrolls inside the dialog.
14447
+ dialogGrid: { maxHeight: "60vh", overflowY: "auto", overflowX: "auto" },
14448
+ filterInput: { minWidth: "260px" },
14449
+ pager: { display: "flex", alignItems: "center", gap: tokens.spacingHorizontalS },
14450
+ grow: { flex: 1 },
14391
14451
  sectionBody: {
14392
14452
  display: "flex",
14393
14453
  flexDirection: "column",
@@ -14406,6 +14466,15 @@ function downloadBytes(fileName, data, contentType = "application/json") {
14406
14466
  anchor.click();
14407
14467
  URL.revokeObjectURL(url);
14408
14468
  }
14469
+ var kindKeyOf = (kind) => KIND_KEY[Number(kind)] ?? "tablemetadata";
14470
+ function cultureLabel(culture) {
14471
+ try {
14472
+ const name = new Intl.DisplayNames(void 0, { type: "language" }).of(culture);
14473
+ return name && name.toLowerCase() !== culture.toLowerCase() ? `${name} (${culture})` : culture;
14474
+ } catch {
14475
+ return culture;
14476
+ }
14477
+ }
14409
14478
  function LocalizationAdmin({ className }) {
14410
14479
  const styles = useStyles12();
14411
14480
  const t = useT();
@@ -14415,6 +14484,7 @@ function LocalizationAdmin({ className }) {
14415
14484
  const [isReloading, setIsReloading] = useState(false);
14416
14485
  const [isDownloading, setIsDownloading] = useState(false);
14417
14486
  const [message, setMessage] = useState();
14487
+ const [mergedDialogCulture, setMergedDialogCulture] = useState(null);
14418
14488
  const loadOverview = useCallback(async () => {
14419
14489
  try {
14420
14490
  const value = await ppp.getLocalizationOverviewAsync();
@@ -14572,17 +14642,44 @@ function LocalizationAdmin({ className }) {
14572
14642
  /* @__PURE__ */ jsx(LocalizationTranslator, {}),
14573
14643
  availableCultures.length > 0 && /* @__PURE__ */ jsx(Section, { headerText: t("app.localization-admin.download-merged-title"), borderVisible: true, children: /* @__PURE__ */ jsx(SectionColumn, { children: /* @__PURE__ */ jsxs("div", { className: styles.sectionBody, children: [
14574
14644
  /* @__PURE__ */ jsx(Text, { children: t("app.localization-admin.download-merged-description") }),
14575
- /* @__PURE__ */ jsx("div", { className: styles.mergedButtons, children: availableCultures.map((culture) => /* @__PURE__ */ jsx(
14576
- Button,
14577
- {
14578
- appearance: "outline",
14579
- disabled: isDownloading,
14580
- icon: /* @__PURE__ */ jsx(ArrowDownloadRegular, {}),
14581
- onClick: () => onDownloadMerged(culture),
14582
- children: culture
14583
- },
14584
- culture
14585
- )) })
14645
+ /* @__PURE__ */ jsx("div", { className: styles.mergedButtons, children: availableCultures.map((culture) => /* @__PURE__ */ jsxs("div", { className: styles.mergedCulture, children: [
14646
+ /* @__PURE__ */ jsx("span", { className: styles.mergedCultureLabel, children: cultureLabel(culture) }),
14647
+ /* @__PURE__ */ jsx(
14648
+ Tooltip,
14649
+ {
14650
+ content: t("app.localization-admin.view-merged-tooltip", [
14651
+ cultureLabel(culture)
14652
+ ]),
14653
+ relationship: "label",
14654
+ children: /* @__PURE__ */ jsx(
14655
+ Button,
14656
+ {
14657
+ appearance: "outline",
14658
+ icon: /* @__PURE__ */ jsx(EyeRegular, {}),
14659
+ onClick: () => setMergedDialogCulture(culture)
14660
+ }
14661
+ )
14662
+ }
14663
+ ),
14664
+ /* @__PURE__ */ jsx(
14665
+ Tooltip,
14666
+ {
14667
+ content: t("app.localization-admin.download-merged-tooltip", [
14668
+ cultureLabel(culture)
14669
+ ]),
14670
+ relationship: "label",
14671
+ children: /* @__PURE__ */ jsx(
14672
+ Button,
14673
+ {
14674
+ appearance: "subtle",
14675
+ disabled: isDownloading,
14676
+ icon: /* @__PURE__ */ jsx(ArrowDownloadRegular, {}),
14677
+ onClick: () => onDownloadMerged(culture)
14678
+ }
14679
+ )
14680
+ }
14681
+ )
14682
+ ] }, culture)) })
14586
14683
  ] }) }) }),
14587
14684
  /* @__PURE__ */ jsx(Section, { headerText: t("app.localization-admin.sources-title"), borderVisible: true, children: /* @__PURE__ */ jsx(SectionColumn, { children: /* @__PURE__ */ jsxs("div", { className: styles.sectionBody, children: [
14588
14685
  /* @__PURE__ */ jsx(Text, { children: t("app.localization-admin.sources-description") }),
@@ -14613,7 +14710,22 @@ function LocalizationAdmin({ className }) {
14613
14710
  ] }) }) })
14614
14711
  ] });
14615
14712
  }
14616
- return /* @__PURE__ */ jsx("div", { className: mergeClasses("ppp-localization-admin", styles.root, className), children: content });
14713
+ return /* @__PURE__ */ jsxs("div", { className: mergeClasses("ppp-localization-admin", styles.root, className), children: [
14714
+ content,
14715
+ mergedDialogCulture !== null && /* @__PURE__ */ jsx(
14716
+ MergedSourcesDialog,
14717
+ {
14718
+ culture: mergedDialogCulture,
14719
+ label: cultureLabel(mergedDialogCulture),
14720
+ onClose: () => setMergedDialogCulture(null),
14721
+ onDownload: () => onDownloadMerged(mergedDialogCulture),
14722
+ isDownloading,
14723
+ ppp,
14724
+ t,
14725
+ styles
14726
+ }
14727
+ )
14728
+ ] });
14617
14729
  }
14618
14730
  function ConfigRow({ styles, label, value }) {
14619
14731
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -14663,6 +14775,177 @@ function SourceRow({
14663
14775
  )) }) })
14664
14776
  ] });
14665
14777
  }
14778
+ var MERGED_PAGE_SIZE = 100;
14779
+ function MergedSourcesDialog({
14780
+ culture,
14781
+ label,
14782
+ onClose,
14783
+ onDownload,
14784
+ isDownloading,
14785
+ ppp,
14786
+ t,
14787
+ styles
14788
+ }) {
14789
+ const [entries, setEntries] = useState(null);
14790
+ const [loadError, setLoadError] = useState();
14791
+ const [filter, setFilter] = useState("");
14792
+ const [page, setPage] = useState(0);
14793
+ const [sortState, setSortState] = useState({
14794
+ sortColumn: "key",
14795
+ sortDirection: "ascending"
14796
+ });
14797
+ useEffect(() => {
14798
+ let cancelled = false;
14799
+ void (async () => {
14800
+ try {
14801
+ const value = await ppp.getMergedLocalizationEntriesAsync(culture);
14802
+ if (!cancelled) {
14803
+ setEntries(value);
14804
+ setLoadError(void 0);
14805
+ }
14806
+ } catch (reason) {
14807
+ if (!cancelled) {
14808
+ setEntries(null);
14809
+ setLoadError(reason instanceof Error ? reason.message : String(reason));
14810
+ }
14811
+ }
14812
+ })();
14813
+ return () => {
14814
+ cancelled = true;
14815
+ };
14816
+ }, [ppp, culture]);
14817
+ const columns = useMemo(
14818
+ () => [
14819
+ createTableColumn({
14820
+ columnId: "key",
14821
+ compare: (a, b) => a.key.localeCompare(b.key),
14822
+ renderHeaderCell: () => t("app.localization-admin.merged-column-key"),
14823
+ renderCell: (item) => item.key
14824
+ }),
14825
+ createTableColumn({
14826
+ columnId: "kind",
14827
+ compare: (a, b) => Number(a.sourceKind) - Number(b.sourceKind),
14828
+ renderHeaderCell: () => t("app.localization-admin.merged-column-kind"),
14829
+ renderCell: (item) => t(`app.localization-admin.kind-${kindKeyOf(item.sourceKind)}`)
14830
+ }),
14831
+ createTableColumn({
14832
+ columnId: "source",
14833
+ compare: (a, b) => a.sourceDisplayName.localeCompare(b.sourceDisplayName),
14834
+ renderHeaderCell: () => t("app.localization-admin.merged-column-source"),
14835
+ renderCell: (item) => item.sourceDisplayName
14836
+ }),
14837
+ createTableColumn({
14838
+ columnId: "value",
14839
+ compare: (a, b) => a.value.localeCompare(b.value),
14840
+ renderHeaderCell: () => t("app.localization-admin.merged-column-value"),
14841
+ renderCell: (item) => item.value
14842
+ })
14843
+ ],
14844
+ [t]
14845
+ );
14846
+ const filtered = useMemo(() => {
14847
+ if (!entries) return [];
14848
+ const q = filter.trim().toLowerCase();
14849
+ const base = q ? entries.filter(
14850
+ (e) => e.key.toLowerCase().includes(q) || e.value.toLowerCase().includes(q) || e.sourceDisplayName.toLowerCase().includes(q)
14851
+ ) : entries;
14852
+ return [...base];
14853
+ }, [entries, filter]);
14854
+ const sorted = useMemo(() => {
14855
+ const column = columns.find((c) => c.columnId === sortState.sortColumn);
14856
+ if (!column?.compare) return filtered;
14857
+ const sign = sortState.sortDirection === "ascending" ? 1 : -1;
14858
+ return [...filtered].sort((a, b) => sign * column.compare(a, b));
14859
+ }, [filtered, columns, sortState]);
14860
+ useEffect(() => {
14861
+ setPage(0);
14862
+ }, [filter, sortState]);
14863
+ const pageCount = Math.max(1, Math.ceil(sorted.length / MERGED_PAGE_SIZE));
14864
+ const currentPage = Math.min(page, pageCount - 1);
14865
+ const paged = sorted.slice(currentPage * MERGED_PAGE_SIZE, currentPage * MERGED_PAGE_SIZE + MERGED_PAGE_SIZE);
14866
+ const columnSizingOptions = useMemo(
14867
+ () => ({
14868
+ key: { minWidth: 200, defaultWidth: 320 },
14869
+ kind: { minWidth: 110, defaultWidth: 140 },
14870
+ source: { minWidth: 200, defaultWidth: 300 },
14871
+ value: { minWidth: 200, defaultWidth: 360 }
14872
+ }),
14873
+ []
14874
+ );
14875
+ return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (_event, data) => !data.open && onClose(), children: /* @__PURE__ */ jsx(DialogSurface, { className: styles.dialogSurface, children: /* @__PURE__ */ jsxs(DialogBody, { children: [
14876
+ /* @__PURE__ */ jsx(DialogTitle, { children: t("app.localization-admin.merged-dialog-title", [label]) }),
14877
+ /* @__PURE__ */ jsx(DialogContent, { children: loadError !== void 0 ? /* @__PURE__ */ jsx(MessageBar, { intent: "error", children: /* @__PURE__ */ jsx(MessageBarBody, { children: t("app.localization-admin.merged-load-failed", [loadError]) }) }) : entries === null ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsxs(Fragment, { children: [
14878
+ /* @__PURE__ */ jsxs("div", { className: styles.dialogToolbar, children: [
14879
+ /* @__PURE__ */ jsx(
14880
+ Input,
14881
+ {
14882
+ className: styles.filterInput,
14883
+ value: filter,
14884
+ onChange: (_event, data) => setFilter(data.value),
14885
+ contentBefore: /* @__PURE__ */ jsx(SearchRegular, {}),
14886
+ placeholder: t("app.localization-admin.merged-filter-placeholder")
14887
+ }
14888
+ ),
14889
+ /* @__PURE__ */ jsx("span", { className: styles.lastReloaded, children: t("app.localization-admin.merged-count", [sorted.length, entries.length]) }),
14890
+ /* @__PURE__ */ jsx("span", { className: styles.grow }),
14891
+ pageCount > 1 && /* @__PURE__ */ jsxs("span", { className: styles.pager, children: [
14892
+ /* @__PURE__ */ jsx(
14893
+ Button,
14894
+ {
14895
+ size: "small",
14896
+ appearance: "subtle",
14897
+ disabled: currentPage === 0,
14898
+ onClick: () => setPage(currentPage - 1),
14899
+ children: t("app.localization-admin.merged-prev")
14900
+ }
14901
+ ),
14902
+ /* @__PURE__ */ jsx("span", { className: styles.lastReloaded, children: t("app.localization-admin.merged-page", [currentPage + 1, pageCount]) }),
14903
+ /* @__PURE__ */ jsx(
14904
+ Button,
14905
+ {
14906
+ size: "small",
14907
+ appearance: "subtle",
14908
+ disabled: currentPage >= pageCount - 1,
14909
+ onClick: () => setPage(currentPage + 1),
14910
+ children: t("app.localization-admin.merged-next")
14911
+ }
14912
+ )
14913
+ ] })
14914
+ ] }),
14915
+ sorted.length === 0 ? /* @__PURE__ */ jsx(MessageBar, { intent: "info", children: /* @__PURE__ */ jsx(MessageBarBody, { children: t("app.localization-admin.merged-empty") }) }) : /* @__PURE__ */ jsx("div", { className: styles.dialogGrid, children: /* @__PURE__ */ jsxs(
14916
+ DataGrid,
14917
+ {
14918
+ items: paged,
14919
+ columns,
14920
+ sortable: true,
14921
+ resizableColumns: true,
14922
+ sortState,
14923
+ onSortChange: (_event, next) => setSortState(next),
14924
+ columnSizingOptions,
14925
+ getRowId: (item) => item.key,
14926
+ size: "small",
14927
+ children: [
14928
+ /* @__PURE__ */ jsx(DataGridHeader, { children: /* @__PURE__ */ jsx(DataGridRow, { children: ({ renderHeaderCell }) => /* @__PURE__ */ jsx(DataGridHeaderCell, { children: renderHeaderCell() }) }) }),
14929
+ /* @__PURE__ */ jsx(DataGridBody, { children: ({ item, rowId }) => /* @__PURE__ */ jsx(DataGridRow, { children: ({ renderCell }) => /* @__PURE__ */ jsx(DataGridCell, { children: renderCell(item) }) }, rowId) })
14930
+ ]
14931
+ }
14932
+ ) })
14933
+ ] }) }),
14934
+ /* @__PURE__ */ jsxs(DialogActions, { children: [
14935
+ /* @__PURE__ */ jsx(
14936
+ Button,
14937
+ {
14938
+ appearance: "primary",
14939
+ icon: /* @__PURE__ */ jsx(ArrowDownloadRegular, {}),
14940
+ disabled: isDownloading,
14941
+ onClick: onDownload,
14942
+ children: t("app.localization-admin.merged-download-button")
14943
+ }
14944
+ ),
14945
+ /* @__PURE__ */ jsx(Button, { appearance: "secondary", icon: /* @__PURE__ */ jsx(DismissRegular, {}), onClick: onClose, children: t("app.buttons.close.label") })
14946
+ ] })
14947
+ ] }) }) });
14948
+ }
14666
14949
  var useStyles13 = makeStyles({
14667
14950
  root: {
14668
14951
  display: "flex",