pixelon 0.3.79 → 0.3.80

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.
@@ -69,6 +69,27 @@ export interface ChatNavigatorConfig {
69
69
  label: string;
70
70
  color?: string;
71
71
  }>;
72
+ /** SPEC-098 — agrupa varios chats en UNA tarjeta con varias puertas.
73
+ *
74
+ * Genérico a propósito: este componente no sabe qué agrupa el consumidor ni por qué. Lo único
75
+ * que exige es que la lista llegue con los items de un grupo JUNTOS y COMPLETOS —el que pagina
76
+ * es quien sabe qué forma un grupo, y partir un grupo entre dos páginas lo rompería—. La
77
+ * etiqueta de cada puerta sale de `tagField`/`tagOptions`, el mismo catálogo que sin agrupar
78
+ * pinta un pill.
79
+ *
80
+ * Un item sin valor en `field` se pinta como una fila normal. */
81
+ group?: {
82
+ /** Campo cuyo valor comparten los items de una tarjeta (items CONSECUTIVOS). */
83
+ field: string;
84
+ /** Campo con el título de la tarjeta; sin él se usa el título del primer chat. */
85
+ titleField?: string;
86
+ /** Campos que, unidos por «·», forman el subtítulo. Los vacíos se omiten. */
87
+ subtitleFields?: string[];
88
+ /** Campo de estado de la tarjeta (se pinta con `serviceStatusOptions`). */
89
+ statusField?: string;
90
+ };
91
+ /** SPEC-098 — `agent_name` es el nombre técnico del agente; en un producto no se enseña. */
92
+ showAgent?: boolean;
72
93
  }
73
94
  export interface ChatNavigatorItem {
74
95
  id: string;
package/dist/index.esm.js CHANGED
@@ -16878,6 +16878,11 @@ function fallbackTitle(item) {
16878
16878
  if (Number.isNaN(d.getTime())) return "Chat";
16879
16879
  return `Chat del ${String(d.getDate()).padStart(2, "0")}/${String(d.getMonth() + 1).padStart(2, "0")}`;
16880
16880
  }
16881
+ function campo(item, field) {
16882
+ if (!field) return "";
16883
+ const v = item[field];
16884
+ return v == null || v === "" ? "" : String(v);
16885
+ }
16881
16886
  function relativeTime(raw) {
16882
16887
  if (!raw) return "";
16883
16888
  const d = new Date(raw);
@@ -16976,6 +16981,25 @@ function ChatNavigator({ config, eventBus }) {
16976
16981
  const listaRef = useRef(null);
16977
16982
  const loadRef = useRef(load);
16978
16983
  loadRef.current = load;
16984
+ useEffect(() => {
16985
+ const host = listaRef.current;
16986
+ const scroller = host?.querySelector(".px-chat-chat-navigator__list");
16987
+ if (!scroller) return;
16988
+ const MARGEN = 8;
16989
+ const revisar = () => {
16990
+ scroller.dataset.desvaneceArriba = scroller.scrollTop > MARGEN ? "1" : "";
16991
+ const abajo = scroller.scrollHeight - scroller.clientHeight - scroller.scrollTop;
16992
+ scroller.dataset.desvaneceAbajo = abajo > MARGEN ? "1" : "";
16993
+ };
16994
+ revisar();
16995
+ scroller.addEventListener("scroll", revisar, { passive: true });
16996
+ const ro = new ResizeObserver(revisar);
16997
+ ro.observe(scroller);
16998
+ return () => {
16999
+ scroller.removeEventListener("scroll", revisar);
17000
+ ro.disconnect();
17001
+ };
17002
+ }, [items, loading]);
16979
17003
  useEffect(() => {
16980
17004
  if (!eventBus) return;
16981
17005
  let t = null;
@@ -17021,6 +17045,82 @@ function ChatNavigator({ config, eventBus }) {
17021
17045
  };
17022
17046
  const page = Math.floor(skip / pageSize) + 1;
17023
17047
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
17048
+ const bloques = useMemo(() => {
17049
+ const field = config.group?.field;
17050
+ const out = [];
17051
+ items.forEach((item) => {
17052
+ const clave = campo(item, field);
17053
+ const ultimo = out[out.length - 1];
17054
+ if (clave && ultimo && ultimo.agrupado && ultimo.clave === clave) {
17055
+ ultimo.chats.push(item);
17056
+ return;
17057
+ }
17058
+ out.push({ clave: clave || item.id, agrupado: !!clave, chats: [item] });
17059
+ });
17060
+ return out;
17061
+ }, [items, config.group?.field]);
17062
+ const insigniaEstado = (valor) => {
17063
+ if (!valor) return null;
17064
+ const opt = (config.serviceStatusOptions ?? []).find((o) => o.value === valor);
17065
+ const c = opt?.color;
17066
+ return /* @__PURE__ */ jsx(
17067
+ "span",
17068
+ {
17069
+ className: "px-chat-chat-navigator__service-badge",
17070
+ style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
17071
+ children: opt?.label ?? valor
17072
+ }
17073
+ );
17074
+ };
17075
+ const contador = (item) => item.unread_proactive_count && item.unread_proactive_count >= 1 ? /* @__PURE__ */ jsx(
17076
+ "span",
17077
+ {
17078
+ className: "px-chat-chat-navigator__badge-count",
17079
+ "aria-label": `${item.unread_proactive_count} mensajes nuevos sin leer`,
17080
+ title: `${item.unread_proactive_count} sin leer`,
17081
+ children: item.unread_proactive_count >= 100 ? "100+" : item.unread_proactive_count
17082
+ }
17083
+ ) : null;
17084
+ const tarjeta = (bloque) => {
17085
+ const g = config.group;
17086
+ const primero = bloque.chats[0];
17087
+ const titulo = campo(primero, g.titleField) || primero.title?.trim() || fallbackTitle(primero);
17088
+ const subtitulo = (g.subtitleFields ?? []).map((f) => campo(primero, f)).filter(Boolean).join(" \xB7 ");
17089
+ const reciente = bloque.chats.reduce(
17090
+ (max, c) => !max || c.updated_date && c.updated_date > max ? c.updated_date : max,
17091
+ null
17092
+ );
17093
+ return /* @__PURE__ */ jsxs("li", { className: "px-chat-chat-navigator__item px-chat-chat-navigator__item--card", children: [
17094
+ /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator__card-head", children: [
17095
+ /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__title", children: titulo }),
17096
+ insigniaEstado(campo(primero, g.statusField))
17097
+ ] }),
17098
+ /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator__card-sub", children: [
17099
+ subtitulo ? /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__card-detail", children: subtitulo }) : null,
17100
+ /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__time", children: relativeTime(reciente) })
17101
+ ] }),
17102
+ /* @__PURE__ */ jsx("div", { className: "px-chat-chat-navigator__doors", children: bloque.chats.map((chat) => {
17103
+ const opt = (config.tagOptions ?? []).find(
17104
+ (o) => o.value === campo(chat, config.tagField)
17105
+ );
17106
+ return /* @__PURE__ */ jsxs(
17107
+ "button",
17108
+ {
17109
+ type: "button",
17110
+ className: "px-chat-chat-navigator__door",
17111
+ style: opt?.color ? { "--px-door-color": opt.color } : void 0,
17112
+ onClick: () => open(chat),
17113
+ children: [
17114
+ opt?.label ?? chat.title?.trim() ?? "Abrir",
17115
+ contador(chat),
17116
+ chat.closed ? /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__door-closed", "aria-hidden": "true" }) : null
17117
+ ]
17118
+ },
17119
+ chat.id
17120
+ );
17121
+ }) })
17122
+ ] }, bloque.clave);
17123
+ };
17024
17124
  return /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator", children: [
17025
17125
  config.search && /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator__search", children: [
17026
17126
  /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "px-chat-chat-navigator__search-icon", children: [
@@ -17101,7 +17201,9 @@ function ChatNavigator({ config, eventBus }) {
17101
17201
  error && !loading && /* @__PURE__ */ jsx("div", { className: "px-chat-chat-navigator__state px-chat-chat-navigator__state--error", children: error }),
17102
17202
  !loading && !error && items.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-chat-chat-navigator__state", children: labels.emptyText ?? "Sin conversaciones" }),
17103
17203
  /* @__PURE__ */ jsxs("div", { className: "px-chat-chat-navigator__list-wrap", ref: listaRef, children: [
17104
- /* @__PURE__ */ jsx("ul", { className: "px-chat-chat-navigator__list", children: items.map((item) => {
17204
+ /* @__PURE__ */ jsx("ul", { className: "px-chat-chat-navigator__list", children: bloques.map((bloque) => {
17205
+ if (bloque.agrupado) return tarjeta(bloque);
17206
+ const item = bloque.chats[0];
17105
17207
  const isRenaming = renamingId === item.id;
17106
17208
  const displayTitle = item.title?.trim() || fallbackTitle(item);
17107
17209
  return /* @__PURE__ */ jsxs("li", { className: "px-chat-chat-navigator__item", children: [
@@ -17128,18 +17230,10 @@ function ChatNavigator({ config, eventBus }) {
17128
17230
  ) : /* @__PURE__ */ jsxs("button", { type: "button", className: "px-chat-chat-navigator__open", onClick: () => open(item), children: [
17129
17231
  /* @__PURE__ */ jsxs("span", { className: "px-chat-chat-navigator__title", children: [
17130
17232
  displayTitle,
17131
- item.unread_proactive_count && item.unread_proactive_count >= 1 ? /* @__PURE__ */ jsx(
17132
- "span",
17133
- {
17134
- className: "px-chat-chat-navigator__badge-count",
17135
- "aria-label": `${item.unread_proactive_count} mensajes nuevos sin leer`,
17136
- title: `${item.unread_proactive_count} sin leer`,
17137
- children: item.unread_proactive_count >= 100 ? "100+" : item.unread_proactive_count
17138
- }
17139
- ) : null
17233
+ contador(item)
17140
17234
  ] }),
17141
17235
  /* @__PURE__ */ jsxs("span", { className: "px-chat-chat-navigator__meta", children: [
17142
- item.agent_name ? /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__agent", children: item.agent_name }) : null,
17236
+ item.agent_name && config.showAgent !== false ? /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__agent", children: item.agent_name }) : null,
17143
17237
  /* @__PURE__ */ jsx("span", { className: "px-chat-chat-navigator__time", children: relativeTime(item.updated_date) }),
17144
17238
  (() => {
17145
17239
  const opt = item.rol_code ? (config.originActorOptions ?? []).find((o) => o.value === item.rol_code) : void 0;
package/dist/index.js CHANGED
@@ -16899,6 +16899,11 @@ function fallbackTitle(item) {
16899
16899
  if (Number.isNaN(d.getTime())) return "Chat";
16900
16900
  return `Chat del ${String(d.getDate()).padStart(2, "0")}/${String(d.getMonth() + 1).padStart(2, "0")}`;
16901
16901
  }
16902
+ function campo(item, field) {
16903
+ if (!field) return "";
16904
+ const v = item[field];
16905
+ return v == null || v === "" ? "" : String(v);
16906
+ }
16902
16907
  function relativeTime(raw) {
16903
16908
  if (!raw) return "";
16904
16909
  const d = new Date(raw);
@@ -16997,6 +17002,25 @@ function ChatNavigator({ config, eventBus }) {
16997
17002
  const listaRef = React.useRef(null);
16998
17003
  const loadRef = React.useRef(load);
16999
17004
  loadRef.current = load;
17005
+ React.useEffect(() => {
17006
+ const host = listaRef.current;
17007
+ const scroller = host?.querySelector(".px-chat-chat-navigator__list");
17008
+ if (!scroller) return;
17009
+ const MARGEN = 8;
17010
+ const revisar = () => {
17011
+ scroller.dataset.desvaneceArriba = scroller.scrollTop > MARGEN ? "1" : "";
17012
+ const abajo = scroller.scrollHeight - scroller.clientHeight - scroller.scrollTop;
17013
+ scroller.dataset.desvaneceAbajo = abajo > MARGEN ? "1" : "";
17014
+ };
17015
+ revisar();
17016
+ scroller.addEventListener("scroll", revisar, { passive: true });
17017
+ const ro = new ResizeObserver(revisar);
17018
+ ro.observe(scroller);
17019
+ return () => {
17020
+ scroller.removeEventListener("scroll", revisar);
17021
+ ro.disconnect();
17022
+ };
17023
+ }, [items, loading]);
17000
17024
  React.useEffect(() => {
17001
17025
  if (!eventBus) return;
17002
17026
  let t = null;
@@ -17042,6 +17066,82 @@ function ChatNavigator({ config, eventBus }) {
17042
17066
  };
17043
17067
  const page = Math.floor(skip / pageSize) + 1;
17044
17068
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
17069
+ const bloques = React.useMemo(() => {
17070
+ const field = config.group?.field;
17071
+ const out = [];
17072
+ items.forEach((item) => {
17073
+ const clave = campo(item, field);
17074
+ const ultimo = out[out.length - 1];
17075
+ if (clave && ultimo && ultimo.agrupado && ultimo.clave === clave) {
17076
+ ultimo.chats.push(item);
17077
+ return;
17078
+ }
17079
+ out.push({ clave: clave || item.id, agrupado: !!clave, chats: [item] });
17080
+ });
17081
+ return out;
17082
+ }, [items, config.group?.field]);
17083
+ const insigniaEstado = (valor) => {
17084
+ if (!valor) return null;
17085
+ const opt = (config.serviceStatusOptions ?? []).find((o) => o.value === valor);
17086
+ const c = opt?.color;
17087
+ return /* @__PURE__ */ jsxRuntime.jsx(
17088
+ "span",
17089
+ {
17090
+ className: "px-chat-chat-navigator__service-badge",
17091
+ style: c ? { color: c, background: `${c}22`, borderColor: `${c}55` } : void 0,
17092
+ children: opt?.label ?? valor
17093
+ }
17094
+ );
17095
+ };
17096
+ const contador = (item) => item.unread_proactive_count && item.unread_proactive_count >= 1 ? /* @__PURE__ */ jsxRuntime.jsx(
17097
+ "span",
17098
+ {
17099
+ className: "px-chat-chat-navigator__badge-count",
17100
+ "aria-label": `${item.unread_proactive_count} mensajes nuevos sin leer`,
17101
+ title: `${item.unread_proactive_count} sin leer`,
17102
+ children: item.unread_proactive_count >= 100 ? "100+" : item.unread_proactive_count
17103
+ }
17104
+ ) : null;
17105
+ const tarjeta = (bloque) => {
17106
+ const g = config.group;
17107
+ const primero = bloque.chats[0];
17108
+ const titulo = campo(primero, g.titleField) || primero.title?.trim() || fallbackTitle(primero);
17109
+ const subtitulo = (g.subtitleFields ?? []).map((f) => campo(primero, f)).filter(Boolean).join(" \xB7 ");
17110
+ const reciente = bloque.chats.reduce(
17111
+ (max, c) => !max || c.updated_date && c.updated_date > max ? c.updated_date : max,
17112
+ null
17113
+ );
17114
+ return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "px-chat-chat-navigator__item px-chat-chat-navigator__item--card", children: [
17115
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-chat-navigator__card-head", children: [
17116
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__title", children: titulo }),
17117
+ insigniaEstado(campo(primero, g.statusField))
17118
+ ] }),
17119
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-chat-navigator__card-sub", children: [
17120
+ subtitulo ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__card-detail", children: subtitulo }) : null,
17121
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__time", children: relativeTime(reciente) })
17122
+ ] }),
17123
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-chat-navigator__doors", children: bloque.chats.map((chat) => {
17124
+ const opt = (config.tagOptions ?? []).find(
17125
+ (o) => o.value === campo(chat, config.tagField)
17126
+ );
17127
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17128
+ "button",
17129
+ {
17130
+ type: "button",
17131
+ className: "px-chat-chat-navigator__door",
17132
+ style: opt?.color ? { "--px-door-color": opt.color } : void 0,
17133
+ onClick: () => open(chat),
17134
+ children: [
17135
+ opt?.label ?? chat.title?.trim() ?? "Abrir",
17136
+ contador(chat),
17137
+ chat.closed ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__door-closed", "aria-hidden": "true" }) : null
17138
+ ]
17139
+ },
17140
+ chat.id
17141
+ );
17142
+ }) })
17143
+ ] }, bloque.clave);
17144
+ };
17045
17145
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-chat-navigator", children: [
17046
17146
  config.search && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-chat-navigator__search", children: [
17047
17147
  /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "px-chat-chat-navigator__search-icon", children: [
@@ -17122,7 +17222,9 @@ function ChatNavigator({ config, eventBus }) {
17122
17222
  error && !loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-chat-navigator__state px-chat-chat-navigator__state--error", children: error }),
17123
17223
  !loading && !error && items.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-chat-navigator__state", children: labels.emptyText ?? "Sin conversaciones" }),
17124
17224
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-chat-navigator__list-wrap", ref: listaRef, children: [
17125
- /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "px-chat-chat-navigator__list", children: items.map((item) => {
17225
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "px-chat-chat-navigator__list", children: bloques.map((bloque) => {
17226
+ if (bloque.agrupado) return tarjeta(bloque);
17227
+ const item = bloque.chats[0];
17126
17228
  const isRenaming = renamingId === item.id;
17127
17229
  const displayTitle = item.title?.trim() || fallbackTitle(item);
17128
17230
  return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "px-chat-chat-navigator__item", children: [
@@ -17149,18 +17251,10 @@ function ChatNavigator({ config, eventBus }) {
17149
17251
  ) : /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "px-chat-chat-navigator__open", onClick: () => open(item), children: [
17150
17252
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-chat-chat-navigator__title", children: [
17151
17253
  displayTitle,
17152
- item.unread_proactive_count && item.unread_proactive_count >= 1 ? /* @__PURE__ */ jsxRuntime.jsx(
17153
- "span",
17154
- {
17155
- className: "px-chat-chat-navigator__badge-count",
17156
- "aria-label": `${item.unread_proactive_count} mensajes nuevos sin leer`,
17157
- title: `${item.unread_proactive_count} sin leer`,
17158
- children: item.unread_proactive_count >= 100 ? "100+" : item.unread_proactive_count
17159
- }
17160
- ) : null
17254
+ contador(item)
17161
17255
  ] }),
17162
17256
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-chat-chat-navigator__meta", children: [
17163
- item.agent_name ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__agent", children: item.agent_name }) : null,
17257
+ item.agent_name && config.showAgent !== false ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__agent", children: item.agent_name }) : null,
17164
17258
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-chat-navigator__time", children: relativeTime(item.updated_date) }),
17165
17259
  (() => {
17166
17260
  const opt = item.rol_code ? (config.originActorOptions ?? []).find((o) => o.value === item.rol_code) : void 0;