@almadar/ui 5.162.0 → 5.164.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.
@@ -27228,6 +27228,7 @@ var init_DashboardLayout = __esm({
27228
27228
  onNotificationClick,
27229
27229
  showThemeToggle = true,
27230
27230
  sidebarFooter,
27231
+ sidebarContent,
27231
27232
  onSignOut: onSignOutProp,
27232
27233
  currentPath,
27233
27234
  layoutMode = "sidebar",
@@ -27353,7 +27354,11 @@ var init_DashboardLayout = __esm({
27353
27354
  {
27354
27355
  as: "nav",
27355
27356
  gap: "none",
27356
- className: cn("flex-1 py-4 space-y-1 overflow-y-auto", isRail ? "px-2" : "px-3"),
27357
+ className: cn(
27358
+ "py-4 space-y-1 overflow-y-auto",
27359
+ sidebarContent && !isRail ? "shrink-0" : "flex-1",
27360
+ isRail ? "px-2" : "px-3"
27361
+ ),
27357
27362
  children: navItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
27358
27363
  NavLink,
27359
27364
  {
@@ -27365,6 +27370,7 @@ var init_DashboardLayout = __esm({
27365
27370
  ))
27366
27371
  }
27367
27372
  ),
27373
+ sidebarContent && !isRail && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 overflow-y-auto border-t border-border dark:border-border px-2 py-3", children: sidebarContent }),
27368
27374
  sidebarFooter && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
27369
27375
  ]
27370
27376
  }
@@ -29774,7 +29780,17 @@ function fileIcon(name) {
29774
29780
  return "file";
29775
29781
  }
29776
29782
  }
29777
- var TreeNodeItem, FlatTreeNodeItem, FileTree;
29783
+ function ancestorsOf(id, byId) {
29784
+ const out = /* @__PURE__ */ new Set();
29785
+ if (!id) return out;
29786
+ let cur = byId.get(id)?.parentId;
29787
+ while (cur && byId.has(cur) && !out.has(cur)) {
29788
+ out.add(cur);
29789
+ cur = byId.get(cur)?.parentId;
29790
+ }
29791
+ return out;
29792
+ }
29793
+ var TreeNodeItem, FlatTreeNodeItem, FlatFileTree, FileTree;
29778
29794
  var init_FileTree = __esm({
29779
29795
  "components/core/molecules/FileTree.tsx"() {
29780
29796
  "use client";
@@ -29865,42 +29881,77 @@ var init_FileTree = __esm({
29865
29881
  indent,
29866
29882
  childrenByParent,
29867
29883
  onNodeSelect,
29868
- defaultExpanded = false
29884
+ onNodeAction,
29885
+ nodeActionIcon,
29886
+ nodeActionLabel,
29887
+ selectedId,
29888
+ look,
29889
+ isExpanded,
29890
+ onToggle
29869
29891
  }) => {
29870
- const [expanded, setExpanded] = React94.useState(defaultExpanded || depth < 1);
29871
29892
  const children = childrenByParent.get(item.id);
29872
29893
  const hasChildren = !!children && children.length > 0;
29894
+ const expanded = hasChildren && isExpanded(item.id, depth);
29895
+ const isSelected = selectedId !== void 0 && selectedId !== "" && item.id === selectedId;
29896
+ const nav = look === "nav";
29873
29897
  const handleClick = React94.useCallback(() => {
29874
- if (hasChildren) setExpanded((prev) => !prev);
29898
+ if (hasChildren && !onNodeSelect) onToggle(item.id, expanded);
29875
29899
  onNodeSelect?.(item.id);
29876
- }, [hasChildren, item.id, onNodeSelect]);
29900
+ }, [hasChildren, expanded, item.id, onNodeSelect, onToggle]);
29901
+ const handleChevron = React94.useCallback((e) => {
29902
+ e.stopPropagation();
29903
+ onToggle(item.id, expanded);
29904
+ }, [item.id, expanded, onToggle]);
29905
+ const handleAction = React94.useCallback((e) => {
29906
+ e.stopPropagation();
29907
+ onNodeAction?.(item.id);
29908
+ }, [item.id, onNodeAction]);
29877
29909
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
29878
29910
  /* @__PURE__ */ jsxRuntime.jsxs(
29879
29911
  Box,
29880
29912
  {
29881
- className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
29913
+ className: `group/treerow flex items-center gap-1.5 px-2 cursor-pointer rounded-sm transition-colors ${nav ? "py-1" : "py-0.5"} ${isSelected ? "bg-primary text-primary-foreground" : nav ? "text-foreground hover:bg-muted" : "hover:bg-muted"}`,
29882
29914
  style: { paddingLeft: depth * indent + 8 },
29883
29915
  onClick: handleClick,
29884
29916
  role: "treeitem",
29917
+ "aria-selected": isSelected,
29885
29918
  "aria-expanded": hasChildren ? expanded : void 0,
29886
29919
  children: [
29887
- hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
29920
+ hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(Box, { onClick: handleChevron, className: "flex items-center flex-shrink-0", role: "button", "aria-label": expanded ? "Collapse" : "Expand", children: /* @__PURE__ */ jsxRuntime.jsx(
29888
29921
  Icon,
29889
29922
  {
29890
29923
  name: expanded ? "chevron-down" : "chevron-right",
29891
29924
  size: "xs",
29892
- className: "text-[var(--color-muted-foreground)] flex-shrink-0"
29925
+ className: isSelected ? "text-inherit" : "text-[var(--color-muted-foreground)]"
29893
29926
  }
29894
- ) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
29927
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
29895
29928
  /* @__PURE__ */ jsxRuntime.jsx(
29896
29929
  Icon,
29897
29930
  {
29898
- name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
29931
+ name: item.icon || (nav ? "file-text" : hasChildren ? expanded ? "folder-open" : "folder" : "file"),
29899
29932
  size: "xs",
29900
- className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
29933
+ className: isSelected ? "text-inherit" : nav || !hasChildren ? "text-[var(--color-muted-foreground)]" : "text-[var(--color-warning)]"
29901
29934
  }
29902
29935
  ),
29903
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
29936
+ /* @__PURE__ */ jsxRuntime.jsx(
29937
+ Typography,
29938
+ {
29939
+ variant: "caption",
29940
+ className: `truncate ${nav ? "text-sm" : "font-mono text-xs"} !text-inherit ${isSelected ? "font-semibold" : ""}`,
29941
+ children: item.label
29942
+ }
29943
+ ),
29944
+ onNodeAction && /* @__PURE__ */ jsxRuntime.jsx(
29945
+ Box,
29946
+ {
29947
+ onClick: handleAction,
29948
+ role: "button",
29949
+ "aria-label": nodeActionLabel ?? "Node action",
29950
+ title: nodeActionLabel,
29951
+ className: `ml-auto flex-shrink-0 rounded-sm p-0.5 opacity-0 group-hover/treerow:opacity-100 transition-opacity ${isSelected ? "hover:bg-primary-foreground/20" : "hover:bg-border"}`,
29952
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: nodeActionIcon ?? "plus", size: "xs", className: "text-inherit" })
29953
+ }
29954
+ )
29904
29955
  ]
29905
29956
  }
29906
29957
  ),
@@ -29911,47 +29962,116 @@ var init_FileTree = __esm({
29911
29962
  depth: depth + 1,
29912
29963
  indent,
29913
29964
  childrenByParent,
29914
- onNodeSelect
29965
+ onNodeSelect,
29966
+ onNodeAction,
29967
+ nodeActionIcon,
29968
+ nodeActionLabel,
29969
+ selectedId,
29970
+ look,
29971
+ isExpanded,
29972
+ onToggle
29915
29973
  },
29916
29974
  child.id
29917
29975
  )) })
29918
29976
  ] });
29919
29977
  };
29978
+ FlatFileTree = ({
29979
+ items,
29980
+ selectedId,
29981
+ look = "files",
29982
+ onNodeSelect,
29983
+ onNodeAction,
29984
+ nodeActionIcon,
29985
+ nodeActionLabel,
29986
+ className,
29987
+ indent = 16
29988
+ }) => {
29989
+ const [overrides, setOverrides] = React94.useState(() => /* @__PURE__ */ new Map());
29990
+ const byId = React94__namespace.default.useMemo(() => new Map(items.map((node) => [node.id, node])), [items]);
29991
+ const selectedAncestors = React94__namespace.default.useMemo(() => ancestorsOf(selectedId, byId), [selectedId, byId]);
29992
+ const lastSelectedRef = React94__namespace.default.useRef(selectedId);
29993
+ React94__namespace.default.useEffect(() => {
29994
+ if (selectedId === lastSelectedRef.current) return;
29995
+ lastSelectedRef.current = selectedId;
29996
+ setOverrides((prev) => {
29997
+ if (![...selectedAncestors].some((a) => prev.get(a) === false)) return prev;
29998
+ const next = new Map(prev);
29999
+ for (const a of selectedAncestors) if (next.get(a) === false) next.delete(a);
30000
+ return next;
30001
+ });
30002
+ }, [selectedId, selectedAncestors]);
30003
+ const isExpanded = React94.useCallback(
30004
+ (id, depth) => overrides.get(id) ?? (depth < 1 || selectedAncestors.has(id)),
30005
+ [overrides, selectedAncestors]
30006
+ );
30007
+ const onToggle = React94.useCallback((id, effective) => {
30008
+ setOverrides((prev) => {
30009
+ const next = new Map(prev);
30010
+ next.set(id, !effective);
30011
+ return next;
30012
+ });
30013
+ }, []);
30014
+ if (items.length === 0) return null;
30015
+ const ids = new Set(items.map((node) => node.id));
30016
+ const childrenByParent = /* @__PURE__ */ new Map();
30017
+ const roots = [];
30018
+ for (const item of items) {
30019
+ if (item.parentId && ids.has(item.parentId)) {
30020
+ const siblings = childrenByParent.get(item.parentId);
30021
+ if (siblings) siblings.push(item);
30022
+ else childrenByParent.set(item.parentId, [item]);
30023
+ } else {
30024
+ roots.push(item);
30025
+ }
30026
+ }
30027
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
30028
+ FlatTreeNodeItem,
30029
+ {
30030
+ item,
30031
+ depth: 0,
30032
+ indent,
30033
+ childrenByParent,
30034
+ onNodeSelect,
30035
+ onNodeAction,
30036
+ nodeActionIcon,
30037
+ nodeActionLabel,
30038
+ selectedId,
30039
+ look,
30040
+ isExpanded,
30041
+ onToggle
30042
+ },
30043
+ item.id
30044
+ )) });
30045
+ };
29920
30046
  FileTree = ({
29921
30047
  tree,
29922
30048
  items,
29923
30049
  selectedPath,
30050
+ selectedId,
30051
+ look = "files",
29924
30052
  onFileSelect,
29925
30053
  onNodeSelect,
30054
+ onNodeAction,
30055
+ nodeActionIcon,
30056
+ nodeActionLabel,
29926
30057
  className,
29927
30058
  indent = 16
29928
30059
  }) => {
29929
30060
  if (items) {
29930
- if (items.length === 0) return null;
29931
- const ids = new Set(items.map((node) => node.id));
29932
- const childrenByParent = /* @__PURE__ */ new Map();
29933
- const roots = [];
29934
- for (const item of items) {
29935
- if (item.parentId && ids.has(item.parentId)) {
29936
- const siblings = childrenByParent.get(item.parentId);
29937
- if (siblings) siblings.push(item);
29938
- else childrenByParent.set(item.parentId, [item]);
29939
- } else {
29940
- roots.push(item);
29941
- }
29942
- }
29943
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
29944
- FlatTreeNodeItem,
30061
+ return /* @__PURE__ */ jsxRuntime.jsx(
30062
+ FlatFileTree,
29945
30063
  {
29946
- item,
29947
- depth: 0,
29948
- indent,
29949
- childrenByParent,
30064
+ items,
30065
+ selectedId,
30066
+ look,
29950
30067
  onNodeSelect,
29951
- defaultExpanded: true
29952
- },
29953
- item.id
29954
- )) });
30068
+ onNodeAction,
30069
+ nodeActionIcon,
30070
+ nodeActionLabel,
30071
+ className,
30072
+ indent
30073
+ }
30074
+ );
29955
30075
  }
29956
30076
  if (!tree || tree.length === 0) return null;
29957
30077
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -35416,6 +35536,33 @@ var init_Lightbox = __esm({
35416
35536
  Lightbox.displayName = "Lightbox";
35417
35537
  }
35418
35538
  });
35539
+
35540
+ // lib/relationLabel.ts
35541
+ function relationLabel(value) {
35542
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date)
35543
+ return null;
35544
+ for (const key of ["name", "title", "label"]) {
35545
+ const candidate = value[key];
35546
+ if (typeof candidate === "string" && candidate !== "") return candidate;
35547
+ }
35548
+ const id = value.id;
35549
+ return id !== void 0 && id !== null ? String(id) : null;
35550
+ }
35551
+ function relationDisplayLabels(value, options) {
35552
+ if (value === void 0 || value === null || value === "") return [];
35553
+ if (Array.isArray(value)) {
35554
+ return value.flatMap((item) => relationDisplayLabels(item, options));
35555
+ }
35556
+ const hydrated = relationLabel(value);
35557
+ if (hydrated !== null) return [hydrated];
35558
+ const raw = String(value);
35559
+ const match = options?.find((opt) => opt.value === raw);
35560
+ return [match ? match.label : raw];
35561
+ }
35562
+ var init_relationLabel = __esm({
35563
+ "lib/relationLabel.ts"() {
35564
+ }
35565
+ });
35419
35566
  function renderIconInput3(icon, props) {
35420
35567
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
35421
35568
  }
@@ -35733,6 +35880,7 @@ var init_TableView = __esm({
35733
35880
  "use client";
35734
35881
  init_cn();
35735
35882
  init_format();
35883
+ init_relationLabel();
35736
35884
  init_getNestedValue();
35737
35885
  init_useEventBus();
35738
35886
  init_Box();
@@ -35746,7 +35894,13 @@ var init_TableView = __esm({
35746
35894
  init_Menu();
35747
35895
  init_useDataDnd();
35748
35896
  tableViewLog = logger.createLogger("almadar:ui:table-view");
35749
- formatCell = (value, format) => formatValue(value, format);
35897
+ formatCell = (value, format) => {
35898
+ if (value !== null && value !== void 0 && typeof value === "object" && !(value instanceof Date)) {
35899
+ const labels = relationDisplayLabels(value);
35900
+ if (labels.length > 0) return labels.join(", ");
35901
+ }
35902
+ return formatValue(value, format);
35903
+ };
35750
35904
  MAX_MEASURED_COL_CH = 32;
35751
35905
  BADGE_CHROME_CH = 4;
35752
35906
  alignClass = {
@@ -39361,6 +39515,35 @@ var init_RichTextEditor = __esm({
39361
39515
  document.execCommand("createLink", false, safe);
39362
39516
  afterEdit();
39363
39517
  }, [afterEdit, t, toolbar.link]);
39518
+ const imageInputRef = React94.useRef(null);
39519
+ const savedRangeRef = React94.useRef(null);
39520
+ const execImage = React94.useCallback(() => {
39521
+ const root = ref.current;
39522
+ const sel = window.getSelection();
39523
+ savedRangeRef.current = root && sel && sel.rangeCount > 0 && root.contains(sel.anchorNode) ? sel.getRangeAt(0).cloneRange() : null;
39524
+ imageInputRef.current?.click();
39525
+ }, []);
39526
+ const handleImageFile = React94.useCallback((e) => {
39527
+ const file = e.target.files?.[0];
39528
+ e.target.value = "";
39529
+ if (!file) return;
39530
+ const reader = new FileReader();
39531
+ reader.onload = () => {
39532
+ const url = typeof reader.result === "string" ? reader.result : "";
39533
+ if (!url.startsWith("data:image/")) return;
39534
+ const root = ref.current;
39535
+ if (!root) return;
39536
+ root.focus();
39537
+ const sel = window.getSelection();
39538
+ if (sel && savedRangeRef.current && root.contains(savedRangeRef.current.startContainer)) {
39539
+ sel.removeAllRanges();
39540
+ sel.addRange(savedRangeRef.current);
39541
+ }
39542
+ document.execCommand("insertImage", false, url);
39543
+ afterEdit();
39544
+ };
39545
+ reader.readAsDataURL(file);
39546
+ }, [afterEdit]);
39364
39547
  const execRule = React94.useCallback(() => {
39365
39548
  document.execCommand("insertHorizontalRule", false);
39366
39549
  afterEdit();
@@ -39373,6 +39556,18 @@ var init_RichTextEditor = __esm({
39373
39556
  }
39374
39557
  return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
39375
39558
  /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
39559
+ /* @__PURE__ */ jsxRuntime.jsx(
39560
+ "input",
39561
+ {
39562
+ ref: imageInputRef,
39563
+ type: "file",
39564
+ accept: "image/*",
39565
+ className: "hidden",
39566
+ "aria-hidden": "true",
39567
+ tabIndex: -1,
39568
+ onChange: handleImageFile
39569
+ }
39570
+ ),
39376
39571
  showToolbar && /* @__PURE__ */ jsxRuntime.jsxs(
39377
39572
  Box,
39378
39573
  {
@@ -39398,6 +39593,7 @@ var init_RichTextEditor = __esm({
39398
39593
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
39399
39594
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
39400
39595
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Link, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
39596
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Image, label: t("richTextEditor.image"), onExec: execImage }),
39401
39597
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Minus, label: t("richTextEditor.divider"), onExec: execRule })
39402
39598
  ]
39403
39599
  }
@@ -39438,6 +39634,8 @@ function DocumentPanel({
39438
39634
  id,
39439
39635
  title,
39440
39636
  subtitle,
39637
+ coverImage,
39638
+ icon,
39441
39639
  value,
39442
39640
  actions,
39443
39641
  maxInlineActions,
@@ -39513,62 +39711,74 @@ function DocumentPanel({
39513
39711
  const emitWithId = (event) => () => {
39514
39712
  if (event) eventBus.emit(`UI:${event}`, { id: recordId });
39515
39713
  };
39516
- return /* @__PURE__ */ jsxRuntime.jsx(Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
39517
- /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
39518
- /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
39519
- titleNode,
39520
- subtitle ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
39521
- ] }),
39522
- /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
39523
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
39524
- /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
39525
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
39526
- editEvent && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
39527
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
39528
- editLabel ?? (t("documentPanel.edit") || "Edit")
39529
- ] }),
39530
- inlineActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
39531
- Button,
39532
- {
39533
- variant: "primary",
39534
- size: "sm",
39535
- onClick: () => fireAction(action),
39536
- "data-testid": `action-${action.event}`,
39537
- children: [
39538
- action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
39539
- action.label
39540
- ]
39541
- },
39542
- idx
39543
- )),
39544
- menuActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
39545
- Menu,
39546
- {
39547
- position: "bottom-end",
39548
- trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
39549
- items: menuActions.map((action) => ({
39550
- label: action.label,
39551
- icon: action.icon,
39552
- variant: action.variant === "danger" ? "danger" : "default",
39553
- onClick: () => fireAction(action)
39554
- }))
39555
- }
39556
- )
39557
- ] }) })
39558
- ] }),
39559
- editing ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsxRuntime.jsx(
39560
- Box,
39714
+ return /* @__PURE__ */ jsxRuntime.jsxs(Card, { variant: "elevated", className: cn("w-full overflow-hidden", className), children: [
39715
+ coverImage ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "h-44 w-full sm:h-52", "data-testid": "document-cover", children: /* @__PURE__ */ jsxRuntime.jsx(
39716
+ "img",
39561
39717
  {
39562
- onClick: emitWithId(editEvent),
39563
- className: cn(
39564
- "rounded-md px-1 -mx-1 min-h-[16rem]",
39565
- editEvent && "cursor-text transition-colors hover:bg-muted/30"
39566
- ),
39567
- "data-testid": "document-body",
39568
- children: value && value !== "" ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
39569
- }
39570
- )
39571
- ] }) });
39718
+ src: coverImage,
39719
+ alt: "",
39720
+ "aria-hidden": "true",
39721
+ className: "h-full w-full object-cover"
39722
+ }
39723
+ ) }) : null,
39724
+ /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
39725
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
39726
+ /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
39727
+ icon ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-muted-foreground", "data-testid": "document-icon", children: renderIconInput4(icon, { size: "lg" }) }) : null,
39728
+ titleNode,
39729
+ subtitle ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
39730
+ ] }),
39731
+ /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
39732
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
39733
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
39734
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
39735
+ editEvent && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
39736
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
39737
+ editLabel ?? (t("documentPanel.edit") || "Edit")
39738
+ ] }),
39739
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
39740
+ Button,
39741
+ {
39742
+ variant: "primary",
39743
+ size: "sm",
39744
+ onClick: () => fireAction(action),
39745
+ "data-testid": `action-${action.event}`,
39746
+ children: [
39747
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
39748
+ action.label
39749
+ ]
39750
+ },
39751
+ idx
39752
+ )),
39753
+ menuActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
39754
+ Menu,
39755
+ {
39756
+ position: "bottom-end",
39757
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
39758
+ items: menuActions.map((action) => ({
39759
+ label: action.label,
39760
+ icon: action.icon,
39761
+ variant: action.variant === "danger" ? "danger" : "default",
39762
+ onClick: () => fireAction(action)
39763
+ }))
39764
+ }
39765
+ )
39766
+ ] }) })
39767
+ ] }),
39768
+ editing ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsxRuntime.jsx(
39769
+ Box,
39770
+ {
39771
+ onClick: emitWithId(editEvent),
39772
+ className: cn(
39773
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
39774
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
39775
+ ),
39776
+ "data-testid": "document-body",
39777
+ children: value && value !== "" ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
39778
+ }
39779
+ )
39780
+ ] })
39781
+ ] });
39572
39782
  }
39573
39783
  var init_DocumentPanel = __esm({
39574
39784
  "components/core/molecules/DocumentPanel.tsx"() {
@@ -39592,17 +39802,16 @@ function renderIconInput5(icon, props) {
39592
39802
  function fieldName(field) {
39593
39803
  return field.name ?? field.key ?? "";
39594
39804
  }
39595
- function relationLabel(value) {
39596
- if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
39597
- for (const key of ["name", "title", "label"]) {
39598
- const candidate = value[key];
39599
- if (typeof candidate === "string" && candidate !== "") return candidate;
39805
+ function relationId(value) {
39806
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
39807
+ const id = value.id;
39808
+ if (id !== void 0 && id !== null) return String(id);
39600
39809
  }
39601
- const id = value.id;
39602
- return id !== void 0 && id !== null ? String(id) : null;
39810
+ return String(value);
39603
39811
  }
39604
39812
  function fieldKind(field, value) {
39605
39813
  if (field.kind) return field.kind;
39814
+ if (field.type === "image") return "image";
39606
39815
  if (field.options && field.options.length > 0) return "select";
39607
39816
  if (typeof value === "boolean" || field.format === "boolean") return "boolean";
39608
39817
  if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
@@ -39612,6 +39821,7 @@ function DocumentDetails({
39612
39821
  entity,
39613
39822
  fields,
39614
39823
  metaCommitEvent,
39824
+ relationEvent,
39615
39825
  title,
39616
39826
  className
39617
39827
  }) {
@@ -39626,11 +39836,51 @@ function DocumentDetails({
39626
39836
  if (!metaCommitEvent || next === current) return;
39627
39837
  eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
39628
39838
  };
39839
+ const relationChip = (item, name, key) => {
39840
+ const label = relationLabel(item) ?? humanizeEnumValue(String(item));
39841
+ if (!relationEvent) {
39842
+ return /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", children: label }, key);
39843
+ }
39844
+ const emitClick = () => eventBus.emit(`UI:${relationEvent}`, { field: name, id: relationId(item), name: label });
39845
+ return /* @__PURE__ */ jsxRuntime.jsx(
39846
+ Badge,
39847
+ {
39848
+ variant: "default",
39849
+ role: "button",
39850
+ tabIndex: 0,
39851
+ className: "cursor-pointer transition-colors hover:bg-accent",
39852
+ onClick: emitClick,
39853
+ onKeyDown: (e) => {
39854
+ if (e.key === "Enter" || e.key === " ") {
39855
+ e.preventDefault();
39856
+ emitClick();
39857
+ }
39858
+ },
39859
+ "data-testid": `relation-chip-${name}`,
39860
+ children: label
39861
+ },
39862
+ key
39863
+ );
39864
+ };
39629
39865
  const renderValue = (field) => {
39630
39866
  const name = fieldName(field);
39631
39867
  const raw = getNestedValue(entity ?? {}, name);
39632
39868
  const kind = fieldKind(field, raw);
39633
39869
  const label = field.label ?? field.header ?? humanizeFieldName(name);
39870
+ if (kind === "image") {
39871
+ const url = typeof raw === "string" ? raw : "";
39872
+ if (!url) return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
39873
+ return /* @__PURE__ */ jsxRuntime.jsx(
39874
+ "img",
39875
+ {
39876
+ src: url,
39877
+ alt: label,
39878
+ loading: "lazy",
39879
+ className: "max-h-32 w-full rounded-md border border-border object-cover",
39880
+ "data-testid": `document-property-image-${name}`
39881
+ }
39882
+ );
39883
+ }
39634
39884
  if (kind === "boolean") {
39635
39885
  return /* @__PURE__ */ jsxRuntime.jsx(
39636
39886
  Switch,
@@ -39660,9 +39910,12 @@ function DocumentDetails({
39660
39910
  if (raw.length === 0) {
39661
39911
  return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
39662
39912
  }
39663
- return /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", children: relationLabel(item) ?? humanizeEnumValue(String(item)) }, i)) });
39913
+ return /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => relationChip(item, name, i)) });
39664
39914
  }
39665
- const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
39915
+ if (raw !== void 0 && raw !== null && raw !== "" && relationLabel(raw) !== null) {
39916
+ return relationChip(raw, name);
39917
+ }
39918
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : formatValue(raw, field.format);
39666
39919
  return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "break-words", children: shown2 });
39667
39920
  }
39668
39921
  if (fieldDraft?.name === name) {
@@ -39731,6 +39984,7 @@ var init_DocumentDetails = __esm({
39731
39984
  init_cn();
39732
39985
  init_format();
39733
39986
  init_getNestedValue();
39987
+ init_relationLabel();
39734
39988
  init_useEventBus();
39735
39989
  init_Box();
39736
39990
  init_Stack();
@@ -44316,7 +44570,18 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
44316
44570
  if (value === void 0 || value === null) return "\u2014";
44317
44571
  const str = String(value);
44318
44572
  switch (fieldType) {
44319
- case "image":
44573
+ case "image": {
44574
+ if (!str) return "\u2014";
44575
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
44576
+ "img",
44577
+ {
44578
+ src: str,
44579
+ alt: formatFieldLabel(fieldName2),
44580
+ className: "max-w-full max-h-64 rounded-md object-contain",
44581
+ loading: "lazy"
44582
+ }
44583
+ ) });
44584
+ }
44320
44585
  case "url": {
44321
44586
  if (str.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str.startsWith("data:image/")) {
44322
44587
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -44329,7 +44594,7 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
44329
44594
  }
44330
44595
  ) });
44331
44596
  }
44332
- if (fieldType === "url" && /^https?:\/\//i.test(str)) {
44597
+ if (/^https?:\/\//i.test(str)) {
44333
44598
  return /* @__PURE__ */ jsxRuntime.jsx(
44334
44599
  "a",
44335
44600
  {
@@ -44415,8 +44680,10 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
44415
44680
  return str;
44416
44681
  }
44417
44682
  case "relation": {
44418
- const match = meta?.options?.find((opt) => opt.value === str);
44419
- return match ? match.label : str;
44683
+ const labels = relationDisplayLabels(value, meta?.options);
44684
+ if (labels.length === 0) return "\u2014";
44685
+ if (labels.length === 1) return labels[0];
44686
+ return /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", wrap: true, children: labels.map((label, i) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", children: label }, i)) });
44420
44687
  }
44421
44688
  case "file": {
44422
44689
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
@@ -44518,6 +44785,7 @@ var init_DetailPanel = __esm({
44518
44785
  init_cn();
44519
44786
  init_format();
44520
44787
  init_getNestedValue();
44788
+ init_relationLabel();
44521
44789
  init_useEventBus();
44522
44790
  init_UploadDropZone();
44523
44791
  formatFieldLabel = humanizeFieldName;
@@ -45176,6 +45444,8 @@ function determineInputType(field) {
45176
45444
  return "url";
45177
45445
  case "file":
45178
45446
  return "file";
45447
+ case "image":
45448
+ return "image";
45179
45449
  case "money":
45180
45450
  return "currency";
45181
45451
  case "number":
@@ -45775,6 +46045,50 @@ var init_Form = __esm({
45775
46045
  }
45776
46046
  }
45777
46047
  );
46048
+ case "image": {
46049
+ const imageUrl = typeof currentValue === "string" ? currentValue : "";
46050
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
46051
+ imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
46052
+ "img",
46053
+ {
46054
+ src: imageUrl,
46055
+ alt: "",
46056
+ "aria-hidden": "true",
46057
+ className: "h-24 w-full max-w-xs rounded-md border border-border object-cover",
46058
+ "data-testid": `image-preview-${fieldName2}`
46059
+ }
46060
+ ) : null,
46061
+ /* @__PURE__ */ jsxRuntime.jsx(
46062
+ UploadDropZone,
46063
+ {
46064
+ accept: "image/*",
46065
+ maxFiles: 1,
46066
+ maxSize: 2 * 1024 * 1024,
46067
+ disabled: isLoading,
46068
+ onFiles: (files) => {
46069
+ const f3 = files[0];
46070
+ if (!f3) return;
46071
+ const reader = new FileReader();
46072
+ reader.onload = () => handleChange(
46073
+ fieldName2,
46074
+ typeof reader.result === "string" ? reader.result : ""
46075
+ );
46076
+ reader.readAsDataURL(f3);
46077
+ }
46078
+ }
46079
+ ),
46080
+ /* @__PURE__ */ jsxRuntime.jsx(
46081
+ Input,
46082
+ {
46083
+ ...commonProps,
46084
+ type: "url",
46085
+ placeholder: t("form.imageUrlFallback"),
46086
+ value: imageUrl,
46087
+ onChange: (e) => handleChange(fieldName2, e.target.value)
46088
+ }
46089
+ )
46090
+ ] });
46091
+ }
45778
46092
  case "date":
45779
46093
  return /* @__PURE__ */ jsxRuntime.jsx(
45780
46094
  Input,