@lv-x-software-house/x_view 1.2.5-dev.46 → 1.2.5-dev.47

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.
Files changed (3) hide show
  1. package/dist/index.js +332 -29
  2. package/dist/index.mjs +332 -29
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -625,12 +625,16 @@ function XViewSidebar({
625
625
  userRole,
626
626
  viewMembers = [],
627
627
  onActivateQuestLens = () => {
628
+ },
629
+ milestones = [],
630
+ onUpdateMilestones = () => {
628
631
  }
629
632
  }) {
630
633
  const [query, setQuery] = (0, import_react2.useState)("");
631
634
  const [showList, setShowList] = (0, import_react2.useState)(false);
632
635
  const [selectedNodeId, setSelectedNodeId] = (0, import_react2.useState)(null);
633
636
  const [isSaving, setIsSaving] = (0, import_react2.useState)(false);
637
+ const [newMilestoneName, setNewMilestoneName] = (0, import_react2.useState)("");
634
638
  const [activeFilters, setActiveFilters] = (0, import_react2.useState)([]);
635
639
  const [isFilterMenuOpen, setIsFilterMenuOpen] = (0, import_react2.useState)(false);
636
640
  const [filterCategory, setFilterCategory] = (0, import_react2.useState)("Type");
@@ -842,6 +846,28 @@ function XViewSidebar({
842
846
  setIsTypeDropdownOpen(false);
843
847
  }
844
848
  };
849
+ const handleAddMilestone = () => {
850
+ if (!newMilestoneName.trim()) return;
851
+ const newMilestone = {
852
+ id: `ms_${Date.now()}`,
853
+ name: newMilestoneName.trim(),
854
+ status: "active"
855
+ };
856
+ onUpdateMilestones([...milestones, newMilestone]);
857
+ setNewMilestoneName("");
858
+ };
859
+ const handleToggleMilestoneStatus = (id) => {
860
+ const updated = milestones.map(
861
+ (ms) => ms.id === id ? { ...ms, status: ms.status === "active" ? "inactive" : "active" } : ms
862
+ );
863
+ onUpdateMilestones(updated);
864
+ };
865
+ const handleRemoveMilestone = (id) => {
866
+ var _a;
867
+ if (!confirm(`Deseja remover o milestone "${(_a = milestones.find((m) => m.id === id)) == null ? void 0 : _a.name}"?`)) return;
868
+ const updated = milestones.filter((ms) => ms.id !== id);
869
+ onUpdateMilestones(updated);
870
+ };
845
871
  const ToggleButton = /* @__PURE__ */ import_react2.default.createElement(
846
872
  "button",
847
873
  {
@@ -1103,7 +1129,54 @@ function XViewSidebar({
1103
1129
  className: "flex-[2] py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-[11px] font-bold rounded-lg shadow-lg shadow-indigo-500/20 transition-all hover:scale-[1.02] active:scale-95"
1104
1130
  },
1105
1131
  "Aplicar Lente"
1106
- )))))), /* @__PURE__ */ import_react2.default.createElement("div", { className: "px-4 py-3 border-t border-white/10 bg-slate-900/30" }, /* @__PURE__ */ import_react2.default.createElement("section", { className: "space-y-2" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ import_react2.default.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400" }, "Navega\xE7\xE3o")), ability.can("update", "Scene") && /* @__PURE__ */ import_react2.default.createElement(
1132
+ ))))), /* @__PURE__ */ import_react2.default.createElement("div", { className: "h-px bg-white/10" }), /* @__PURE__ */ import_react2.default.createElement("section", { className: "space-y-3" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ import_react2.default.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400" }, "Milestones")), /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ import_react2.default.createElement(
1133
+ "input",
1134
+ {
1135
+ type: "text",
1136
+ value: newMilestoneName,
1137
+ onChange: (e) => setNewMilestoneName(e.target.value),
1138
+ onKeyDown: (e) => e.key === "Enter" && handleAddMilestone(),
1139
+ placeholder: "Novo Milestone...",
1140
+ className: "flex-1 bg-slate-900/50 border border-white/10 rounded-md px-2 py-1.5 text-xs text-slate-100 placeholder:text-slate-600 focus:outline-none focus:border-indigo-500/50"
1141
+ }
1142
+ ), /* @__PURE__ */ import_react2.default.createElement(
1143
+ "button",
1144
+ {
1145
+ onClick: handleAddMilestone,
1146
+ disabled: !newMilestoneName.trim(),
1147
+ className: "p-1.5 rounded-md bg-indigo-500/10 hover:bg-indigo-500/20 border border-indigo-500/30 text-indigo-300 disabled:opacity-30 transition-colors"
1148
+ },
1149
+ /* @__PURE__ */ import_react2.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("line", { x1: "12", y1: "5", x2: "12", y2: "19" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "5", y1: "12", x2: "19", y2: "12" }))
1150
+ )), /* @__PURE__ */ import_react2.default.createElement("div", { className: "space-y-1.5 max-h-[200px] overflow-y-auto custom-scrollbar pr-1" }, milestones.length > 0 ? milestones.map((ms) => /* @__PURE__ */ import_react2.default.createElement(
1151
+ "div",
1152
+ {
1153
+ key: ms.id,
1154
+ className: `group flex items-center justify-between px-2.5 py-2 rounded-lg border transition-all ${ms.status === "active" ? "bg-indigo-500/5 border-indigo-500/20" : "bg-slate-900/30 border-white/5 opacity-60"}`
1155
+ },
1156
+ /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ import_react2.default.createElement(
1157
+ "div",
1158
+ {
1159
+ className: `w-1.5 h-1.5 rounded-full shrink-0 ${ms.status === "active" ? "bg-indigo-400 shadow-[0_0_8px_rgba(129,140,248,0.5)]" : "bg-slate-600"}`
1160
+ }
1161
+ ), /* @__PURE__ */ import_react2.default.createElement("span", { className: `text-[11px] truncate font-medium ${ms.status === "active" ? "text-slate-100" : "text-slate-500 line-through"}` }, ms.name)),
1162
+ /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity" }, /* @__PURE__ */ import_react2.default.createElement(
1163
+ "button",
1164
+ {
1165
+ onClick: () => handleToggleMilestoneStatus(ms.id),
1166
+ className: `p-1 rounded hover:bg-white/10 transition-colors ${ms.status === "active" ? "text-slate-400" : "text-indigo-400"}`,
1167
+ title: ms.status === "active" ? "Inativar" : "Ativar"
1168
+ },
1169
+ ms.status === "active" ? /* @__PURE__ */ import_react2.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("rect", { x: "6", y: "4", width: "4", height: "16" }), /* @__PURE__ */ import_react2.default.createElement("rect", { x: "14", y: "4", width: "4", height: "16" })) : /* @__PURE__ */ import_react2.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("polygon", { points: "5 3 19 12 5 21 5 3" }))
1170
+ ), /* @__PURE__ */ import_react2.default.createElement(
1171
+ "button",
1172
+ {
1173
+ onClick: () => handleRemoveMilestone(ms.id),
1174
+ className: "p-1 rounded hover:bg-red-500/20 text-slate-500 hover:text-red-400 transition-colors",
1175
+ title: "Remover"
1176
+ },
1177
+ /* @__PURE__ */ import_react2.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react2.default.createElement("polyline", { points: "3 6 5 6 21 6" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "10", y1: "11", x2: "10", y2: "17" }), /* @__PURE__ */ import_react2.default.createElement("line", { x1: "14", y1: "11", x2: "14", y2: "17" }))
1178
+ ))
1179
+ )) : /* @__PURE__ */ import_react2.default.createElement("p", { className: "text-[10px] text-slate-600 text-center py-2 italic font-light italic" }, "Nenhum milestone definido")))), /* @__PURE__ */ import_react2.default.createElement("div", { className: "px-4 py-3 border-t border-white/10 bg-slate-900/30" }, /* @__PURE__ */ import_react2.default.createElement("section", { className: "space-y-2" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ import_react2.default.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400" }, "Navega\xE7\xE3o")), ability.can("update", "Scene") && /* @__PURE__ */ import_react2.default.createElement(
1107
1180
  "button",
1108
1181
  {
1109
1182
  onClick: handleSaveClick,
@@ -2332,7 +2405,8 @@ var userActionHandlers = {
2332
2405
  nodes: sceneDataRef.current.nodes,
2333
2406
  links: sceneDataRef.current.links,
2334
2407
  quest_nodes: specificParentData.nodes,
2335
- quest_links: specificParentData.links
2408
+ quest_links: specificParentData.links,
2409
+ milestones: sceneDataRef.current.milestones || []
2336
2410
  };
2337
2411
  await context.actions.save_view_data(
2338
2412
  context.sceneSaveUrl,
@@ -2590,7 +2664,8 @@ var userActionHandlers = {
2590
2664
  nodes: sceneDataRef.current.nodes,
2591
2665
  links: sceneDataRef.current.links,
2592
2666
  quest_nodes: specificParentData.nodes,
2593
- quest_links: specificParentData.links
2667
+ quest_links: specificParentData.links,
2668
+ milestones: sceneDataRef.current.milestones || []
2594
2669
  };
2595
2670
  await context.actions.save_view_data(
2596
2671
  context.sceneSaveUrl,
@@ -2746,7 +2821,8 @@ var userActionHandlers = {
2746
2821
  nodes: sceneDataRef.current.nodes,
2747
2822
  links: sceneDataRef.current.links,
2748
2823
  quest_nodes: data.nodes,
2749
- quest_links: data.links
2824
+ quest_links: data.links,
2825
+ milestones: sceneDataRef.current.milestones || []
2750
2826
  };
2751
2827
  return context.actions.save_view_data(
2752
2828
  context.sceneSaveUrl,
@@ -3360,6 +3436,7 @@ var userActionHandlers = {
3360
3436
  links: sceneDataRef.current.links,
3361
3437
  quest_nodes: specificParentData.nodes,
3362
3438
  quest_links: specificParentData.links,
3439
+ milestones: sceneDataRef.current.milestones || [],
3363
3440
  quest_counter: sceneDataRef.current.quest_counter || 1
3364
3441
  };
3365
3442
  await context.actions.save_view_data(
@@ -3432,7 +3509,8 @@ var userActionHandlers = {
3432
3509
  links: sceneDataRef.current.links,
3433
3510
  quest_nodes: specificParentData.nodes,
3434
3511
  quest_links: specificParentData.links,
3435
- quest_counter: sceneDataRef.current.quest_counter || 1
3512
+ quest_counter: sceneDataRef.current.quest_counter || 1,
3513
+ milestones: sceneDataRef.current.milestones || []
3436
3514
  };
3437
3515
  await context.actions.save_view_data(
3438
3516
  context.sceneSaveUrl,
@@ -9468,11 +9546,15 @@ function InSceneQuestForm({
9468
9546
  questCounter = 1,
9469
9547
  // NOVA PROP
9470
9548
  viewMembers = [],
9471
- session = null
9549
+ session = null,
9550
+ milestones = [],
9551
+ onUpdateMilestones = () => {
9552
+ }
9472
9553
  }) {
9473
- var _a, _b;
9554
+ var _a, _b, _c;
9474
9555
  const [name, setName] = (0, import_react17.useState)("");
9475
9556
  const [assigneeId, setAssigneeId] = (0, import_react17.useState)("");
9557
+ const [milestoneId, setMilestoneId] = (0, import_react17.useState)("");
9476
9558
  const [types, setTypes] = (0, import_react17.useState)(["quest"]);
9477
9559
  const [typeInput, setTypeInput] = (0, import_react17.useState)("");
9478
9560
  const [status, setStatus] = (0, import_react17.useState)("Backlog");
@@ -9494,6 +9576,32 @@ function InSceneQuestForm({
9494
9576
  const [customProps, setCustomProps] = (0, import_react17.useState)([]);
9495
9577
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = (0, import_react17.useState)(false);
9496
9578
  const propsEndRef = (0, import_react17.useRef)(null);
9579
+ const [isMilestoneDropdownOpen, setIsMilestoneDropdownOpen] = (0, import_react17.useState)(false);
9580
+ const [milestoneSearchQuery, setMilestoneSearchQuery] = (0, import_react17.useState)("");
9581
+ const activeMilestones = import_react17.default.useMemo(
9582
+ () => (milestones || []).filter((ms) => ms.status === "active"),
9583
+ [milestones]
9584
+ );
9585
+ const filteredMilestones = import_react17.default.useMemo(() => {
9586
+ if (!milestoneSearchQuery.trim()) return activeMilestones;
9587
+ return activeMilestones.filter(
9588
+ (ms) => ms.name.toLowerCase().includes(milestoneSearchQuery.toLowerCase())
9589
+ );
9590
+ }, [activeMilestones, milestoneSearchQuery]);
9591
+ const handleSelectMilestone = (id) => {
9592
+ setMilestoneId(id);
9593
+ setIsMilestoneDropdownOpen(false);
9594
+ setMilestoneSearchQuery("");
9595
+ };
9596
+ const handleCreateMilestone = async (name2) => {
9597
+ if (!name2.trim()) return;
9598
+ const newId = `ms_${Date.now()}`;
9599
+ const newMs = { id: newId, name: name2.trim(), status: "active" };
9600
+ await onUpdateMilestones([...milestones, newMs]);
9601
+ setMilestoneId(newId);
9602
+ setIsMilestoneDropdownOpen(false);
9603
+ setMilestoneSearchQuery("");
9604
+ };
9497
9605
  const standardizedName = `${viewName} - ${questCounter} - \xBB ${name || "Nova Quest"}`;
9498
9606
  const handleAddProp = () => {
9499
9607
  const newProp = createNewCustomProperty(customProps);
@@ -9561,6 +9669,7 @@ function InSceneQuestForm({
9561
9669
  timeline: initialTimeline,
9562
9670
  Date: questDate ? { ...questDate, confirmed: true } : null,
9563
9671
  recurrence_config: recurrenceConfig,
9672
+ milestone_id: milestoneId || null,
9564
9673
  ...additionalData
9565
9674
  });
9566
9675
  };
@@ -9664,6 +9773,63 @@ function InSceneQuestForm({
9664
9773
  }
9665
9774
  ),
9666
9775
  s
9776
+ )))))), /* @__PURE__ */ import_react17.default.createElement("div", { className: "space-y-1.5 relative mt-2" }, /* @__PURE__ */ import_react17.default.createElement("label", { className: "text-xs text-slate-300 font-medium uppercase tracking-wider" }, "Milestone"), /* @__PURE__ */ import_react17.default.createElement("div", { className: "relative" }, /* @__PURE__ */ import_react17.default.createElement(
9777
+ "button",
9778
+ {
9779
+ type: "button",
9780
+ onClick: () => setIsMilestoneDropdownOpen(!isMilestoneDropdownOpen),
9781
+ className: "w-full bg-slate-800/70 p-2.5 text-sm rounded-lg border border-white/10 hover:border-white/20 focus:outline-none focus:ring-2 focus:ring-indigo-400/60 transition-colors flex items-center justify-between"
9782
+ },
9783
+ /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react17.default.createElement(import_fi15.FiTarget, { className: "text-slate-400", size: 14 }), /* @__PURE__ */ import_react17.default.createElement("span", { className: "text-slate-200 font-medium" }, ((_a = (milestones || []).find((m) => m.id === milestoneId)) == null ? void 0 : _a.name) || "Nenhum")),
9784
+ /* @__PURE__ */ import_react17.default.createElement(
9785
+ import_fi15.FiChevronDown,
9786
+ {
9787
+ className: `text-slate-400 transition-transform duration-200 ${isMilestoneDropdownOpen ? "rotate-180" : ""}`
9788
+ }
9789
+ )
9790
+ ), isMilestoneDropdownOpen && /* @__PURE__ */ import_react17.default.createElement(import_react17.default.Fragment, null, /* @__PURE__ */ import_react17.default.createElement(
9791
+ "div",
9792
+ {
9793
+ className: "fixed inset-0 z-40",
9794
+ onClick: () => {
9795
+ setIsMilestoneDropdownOpen(false);
9796
+ setMilestoneSearchQuery("");
9797
+ }
9798
+ }
9799
+ ), /* @__PURE__ */ import_react17.default.createElement("div", { className: "absolute top-full left-0 mt-1.5 w-full bg-slate-900 border border-white/10 rounded-lg shadow-[0_8px_30px_rgba(0,0,0,0.5)] z-50 overflow-hidden flex flex-col" }, /* @__PURE__ */ import_react17.default.createElement("div", { className: "p-2 border-b border-white/5 bg-white/5 flex items-center gap-2" }, /* @__PURE__ */ import_react17.default.createElement(import_fi15.FiSearch, { className: "text-slate-500", size: 14 }), /* @__PURE__ */ import_react17.default.createElement(
9800
+ "input",
9801
+ {
9802
+ type: "text",
9803
+ autoFocus: true,
9804
+ placeholder: "Buscar ou criar milestone...",
9805
+ value: milestoneSearchQuery,
9806
+ onChange: (e) => setMilestoneSearchQuery(e.target.value),
9807
+ className: "bg-transparent border-none outline-none text-xs text-white placeholder-slate-500 w-full",
9808
+ onClick: (e) => e.stopPropagation()
9809
+ }
9810
+ )), /* @__PURE__ */ import_react17.default.createElement("ul", { className: "max-h-48 overflow-y-auto custom-scrollbar" }, /* @__PURE__ */ import_react17.default.createElement(
9811
+ "li",
9812
+ {
9813
+ onClick: () => handleSelectMilestone(""),
9814
+ className: `px-3 py-2.5 text-sm cursor-pointer transition-colors flex items-center gap-2 ${!milestoneId ? "bg-indigo-500/20 text-white" : "text-slate-300 hover:bg-white/5 hover:text-white"}`
9815
+ },
9816
+ "Nenhum"
9817
+ ), filteredMilestones.map((ms) => /* @__PURE__ */ import_react17.default.createElement(
9818
+ "li",
9819
+ {
9820
+ key: ms.id,
9821
+ onClick: () => handleSelectMilestone(ms.id),
9822
+ className: `px-3 py-2.5 text-sm cursor-pointer transition-colors flex items-center gap-2 ${milestoneId === ms.id ? "bg-indigo-500/20 text-white" : "text-slate-300 hover:bg-white/5 hover:text-white"}`
9823
+ },
9824
+ ms.name
9825
+ )), milestoneSearchQuery.trim() && !filteredMilestones.some((ms) => ms.name.toLowerCase() === milestoneSearchQuery.toLowerCase().trim()) && /* @__PURE__ */ import_react17.default.createElement(
9826
+ "li",
9827
+ {
9828
+ onClick: () => handleCreateMilestone(milestoneSearchQuery),
9829
+ className: "px-3 py-3 text-sm cursor-pointer bg-indigo-600/20 text-indigo-200 hover:bg-indigo-600/40 transition-colors flex flex-col gap-0.5"
9830
+ },
9831
+ /* @__PURE__ */ import_react17.default.createElement("span", { className: "text-[10px] uppercase font-bold opacity-60" }, "Criar Novo Milestone"),
9832
+ /* @__PURE__ */ import_react17.default.createElement("span", { className: "font-medium" }, '"', milestoneSearchQuery, '"')
9667
9833
  )))))), /* @__PURE__ */ import_react17.default.createElement("div", { className: "space-y-1.5 relative mt-2" }, /* @__PURE__ */ import_react17.default.createElement("label", { className: "text-xs text-slate-300 font-medium uppercase tracking-wider" }, "Assignee (Respons\xE1vel)"), /* @__PURE__ */ import_react17.default.createElement("div", { className: "relative" }, /* @__PURE__ */ import_react17.default.createElement(
9668
9834
  "button",
9669
9835
  {
@@ -9671,7 +9837,7 @@ function InSceneQuestForm({
9671
9837
  onClick: () => setIsAssigneeDropdownOpen(!isAssigneeDropdownOpen),
9672
9838
  className: "w-full bg-slate-800/70 p-2.5 text-sm rounded-lg border border-white/10 hover:border-white/20 focus:outline-none focus:ring-2 focus:ring-indigo-400/60 transition-colors flex items-center justify-between"
9673
9839
  },
9674
- /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react17.default.createElement(import_fi15.FiUser, { className: "text-slate-400", size: 14 }), /* @__PURE__ */ import_react17.default.createElement("span", { className: "text-slate-200 font-medium" }, ((_a = viewMembers.find((m) => m.id === assigneeId)) == null ? void 0 : _a.name) || ((_b = viewMembers.find((m) => m.id === assigneeId)) == null ? void 0 : _b.email) || "Nenhum")),
9840
+ /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react17.default.createElement(import_fi15.FiUser, { className: "text-slate-400", size: 14 }), /* @__PURE__ */ import_react17.default.createElement("span", { className: "text-slate-200 font-medium" }, ((_b = viewMembers.find((m) => m.id === assigneeId)) == null ? void 0 : _b.name) || ((_c = viewMembers.find((m) => m.id === assigneeId)) == null ? void 0 : _c.email) || "Nenhum")),
9675
9841
  /* @__PURE__ */ import_react17.default.createElement(
9676
9842
  import_fi15.FiChevronDown,
9677
9843
  {
@@ -10643,9 +10809,12 @@ function QuestDetailsPanel({
10643
10809
  userRole,
10644
10810
  currentDatasetName,
10645
10811
  viewMembers = [],
10646
- currentUser
10812
+ currentUser,
10813
+ milestones = [],
10814
+ onUpdateMilestones = () => {
10815
+ }
10647
10816
  }) {
10648
- var _a, _b;
10817
+ var _a, _b, _c;
10649
10818
  const initialRawTitle = (node == null ? void 0 : node.raw_title) || (((_a = node == null ? void 0 : node.name) == null ? void 0 : _a.includes(" - \xBB ")) ? node.name.split(" - \xBB ")[1] : node == null ? void 0 : node.name) || "";
10650
10819
  const [rawTitle, setRawTitle] = (0, import_react20.useState)(initialRawTitle);
10651
10820
  const prefixParts = ((node == null ? void 0 : node.name) || "").split(" - \xBB ");
@@ -10658,6 +10827,7 @@ function QuestDetailsPanel({
10658
10827
  const [assigneeId, setAssigneeId] = (0, import_react20.useState)((node == null ? void 0 : node.assignee_id) || "");
10659
10828
  const [description, setDescription] = (0, import_react20.useState)((node == null ? void 0 : node.description) ?? "");
10660
10829
  const [intensity, setIntensity] = (0, import_react20.useState)((node == null ? void 0 : node.intensity) !== void 0 ? node.intensity : 0);
10830
+ const [milestoneId, setMilestoneId] = (0, import_react20.useState)((node == null ? void 0 : node.milestone_id) || "");
10661
10831
  const [isStatusDropdownOpen, setIsStatusDropdownOpen] = (0, import_react20.useState)(false);
10662
10832
  const [isAssigneeDropdownOpen, setIsAssigneeDropdownOpen] = (0, import_react20.useState)(false);
10663
10833
  const [assigneeSearchQuery, setAssigneeSearchQuery] = (0, import_react20.useState)("");
@@ -10695,6 +10865,35 @@ function QuestDetailsPanel({
10695
10865
  minWidth: 320,
10696
10866
  maxWidth: maxPanelW
10697
10867
  });
10868
+ const [isMilestoneDropdownOpen, setIsMilestoneDropdownOpen] = (0, import_react20.useState)(false);
10869
+ const [milestoneSearchQuery, setMilestoneSearchQuery] = (0, import_react20.useState)("");
10870
+ const activeMilestones = import_react20.default.useMemo(
10871
+ () => (milestones || []).filter((ms) => ms.status === "active"),
10872
+ [milestones]
10873
+ );
10874
+ const filteredMilestones = import_react20.default.useMemo(() => {
10875
+ const search = milestoneSearchQuery.toLowerCase().trim();
10876
+ if (!search) return activeMilestones;
10877
+ return activeMilestones.filter(
10878
+ (ms) => ms.name.toLowerCase().includes(search)
10879
+ );
10880
+ }, [activeMilestones, milestoneSearchQuery]);
10881
+ const handleSelectMilestone = (id) => {
10882
+ setMilestoneId(id);
10883
+ setIsMilestoneDropdownOpen(false);
10884
+ setMilestoneSearchQuery("");
10885
+ setHasUnsavedChanges(true);
10886
+ };
10887
+ const handleCreateMilestone = async (name) => {
10888
+ if (!name.trim()) return;
10889
+ const newId = `ms_${Date.now()}`;
10890
+ const newMs = { id: newId, name: name.trim(), status: "active" };
10891
+ await onUpdateMilestones([...milestones, newMs]);
10892
+ setMilestoneId(newId);
10893
+ setIsMilestoneDropdownOpen(false);
10894
+ setMilestoneSearchQuery("");
10895
+ setHasUnsavedChanges(true);
10896
+ };
10698
10897
  (0, import_react20.useEffect)(() => {
10699
10898
  setWidth(isReadMode ? 700 : 440);
10700
10899
  }, [isReadMode, setWidth]);
@@ -10720,6 +10919,7 @@ function QuestDetailsPanel({
10720
10919
  setReturnAssigneeId("");
10721
10920
  setDescription((node == null ? void 0 : node.description) ?? "");
10722
10921
  setIntensity((node == null ? void 0 : node.intensity) !== void 0 ? node.intensity : 0);
10922
+ setMilestoneId((node == null ? void 0 : node.milestone_id) || "");
10723
10923
  setExistingSections((node == null ? void 0 : node.description_sections) || []);
10724
10924
  setCustomProps(extractCustomPropsFromNode(node || {}));
10725
10925
  setReviewStack((node == null ? void 0 : node.reviewStack) || []);
@@ -10732,6 +10932,7 @@ function QuestDetailsPanel({
10732
10932
  custom_date: "",
10733
10933
  next_assignee_id: ""
10734
10934
  });
10935
+ setMilestoneId((node == null ? void 0 : node.milestone_id) || "");
10735
10936
  setHasUnsavedChanges(false);
10736
10937
  }
10737
10938
  }, [node]);
@@ -11030,7 +11231,8 @@ ${noteText}` : noteText : entry.notes,
11030
11231
  reviewStack: currentReviewStack,
11031
11232
  timeline: currentTimeline,
11032
11233
  ...extrasObj,
11033
- version_node: node.version_node
11234
+ version_node: node.version_node,
11235
+ milestone_id: milestoneId || null
11034
11236
  };
11035
11237
  await onSave(dataToSave, keepOpen);
11036
11238
  onDataUpdate(dataToSave);
@@ -11232,7 +11434,51 @@ ${noteText}` : noteText : entry.notes,
11232
11434
  maxLength: "10",
11233
11435
  className: "flex-1 bg-slate-800/70 p-2 text-xs rounded-lg border border-white/10 focus:outline-none focus:ring-1 focus:ring-indigo-400/60"
11234
11436
  }
11235
- ))))), /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1.5 relative mt-2" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-xs text-slate-300" }, "Status da Quest"), /* @__PURE__ */ import_react20.default.createElement("div", { className: "relative" }, /* @__PURE__ */ import_react20.default.createElement(
11437
+ ))))), /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1.5 relative mt-2" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-xs text-slate-300" }, "Milestone"), /* @__PURE__ */ import_react20.default.createElement("div", { className: "relative" }, /* @__PURE__ */ import_react20.default.createElement(
11438
+ "button",
11439
+ {
11440
+ type: "button",
11441
+ onClick: () => canEdit && setIsMilestoneDropdownOpen(!isMilestoneDropdownOpen),
11442
+ disabled: !canEdit,
11443
+ className: `w-full bg-slate-800/70 p-2.5 text-sm rounded-lg border border-white/10 transition-colors flex items-center justify-between ${canEdit ? "hover:border-white/20 focus:ring-2 focus:ring-indigo-400/60 cursor-pointer" : "cursor-default opacity-80"}`
11444
+ },
11445
+ /* @__PURE__ */ import_react20.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react20.default.createElement(import_fi18.FiTarget, { className: "text-slate-400", size: 14 }), /* @__PURE__ */ import_react20.default.createElement("span", { className: "text-slate-200 font-medium" }, ((_b = (milestones || []).find((m) => m.id === milestoneId)) == null ? void 0 : _b.name) || "Nenhum")),
11446
+ canEdit && /* @__PURE__ */ import_react20.default.createElement(import_fi18.FiChevronDown, { className: `text-slate-400 transition-transform duration-200 ${isMilestoneDropdownOpen ? "rotate-180" : ""}` })
11447
+ ), isMilestoneDropdownOpen && canEdit && /* @__PURE__ */ import_react20.default.createElement(import_react20.default.Fragment, null, /* @__PURE__ */ import_react20.default.createElement("div", { className: "fixed inset-0 z-40", onClick: () => setIsMilestoneDropdownOpen(false) }), /* @__PURE__ */ import_react20.default.createElement("div", { className: "absolute top-full left-0 mt-1.5 w-full bg-slate-900 border border-white/10 rounded-lg shadow-[0_8px_30px_rgba(0,0,0,0.5)] z-50 overflow-hidden flex flex-col" }, /* @__PURE__ */ import_react20.default.createElement("div", { className: "p-2 border-b border-white/5 bg-white/5 flex items-center gap-2" }, /* @__PURE__ */ import_react20.default.createElement(import_fi18.FiSearch, { className: "text-slate-500", size: 14 }), /* @__PURE__ */ import_react20.default.createElement(
11448
+ "input",
11449
+ {
11450
+ type: "text",
11451
+ autoFocus: true,
11452
+ placeholder: "Buscar ou criar milestone...",
11453
+ value: milestoneSearchQuery,
11454
+ onChange: (e) => setMilestoneSearchQuery(e.target.value),
11455
+ className: "bg-transparent border-none outline-none text-xs text-white placeholder-slate-500 w-full",
11456
+ onClick: (e) => e.stopPropagation()
11457
+ }
11458
+ )), /* @__PURE__ */ import_react20.default.createElement("ul", { className: "max-h-48 overflow-y-auto custom-scrollbar" }, /* @__PURE__ */ import_react20.default.createElement(
11459
+ "li",
11460
+ {
11461
+ onClick: () => handleSelectMilestone(""),
11462
+ className: `px-3 py-2.5 text-sm cursor-pointer transition-colors flex items-center gap-2 ${!milestoneId ? "bg-indigo-500/20 text-white" : "text-slate-300 hover:bg-white/5 hover:text-white"}`
11463
+ },
11464
+ "Nenhum"
11465
+ ), filteredMilestones.map((ms) => /* @__PURE__ */ import_react20.default.createElement(
11466
+ "li",
11467
+ {
11468
+ key: ms.id,
11469
+ onClick: () => handleSelectMilestone(ms.id),
11470
+ className: `px-3 py-2.5 text-sm cursor-pointer transition-colors flex items-center gap-2 ${milestoneId === ms.id ? "bg-indigo-500/20 text-white" : "text-slate-300 hover:bg-white/5 hover:text-white"}`
11471
+ },
11472
+ ms.name
11473
+ )), milestoneSearchQuery.trim() && !filteredMilestones.some((ms) => ms.name.toLowerCase() === milestoneSearchQuery.toLowerCase().trim()) && /* @__PURE__ */ import_react20.default.createElement(
11474
+ "li",
11475
+ {
11476
+ onClick: () => handleCreateMilestone(milestoneSearchQuery),
11477
+ className: "px-3 py-3 text-sm cursor-pointer bg-indigo-600/20 text-indigo-200 hover:bg-indigo-600/40 transition-colors flex flex-col gap-0.5"
11478
+ },
11479
+ /* @__PURE__ */ import_react20.default.createElement("span", { className: "text-[10px] uppercase font-bold opacity-60" }, "Criar Novo Milestone"),
11480
+ /* @__PURE__ */ import_react20.default.createElement("span", { className: "font-medium" }, '"', milestoneSearchQuery, '"')
11481
+ )))))), /* @__PURE__ */ import_react20.default.createElement("div", { className: "space-y-1.5 relative mt-2" }, /* @__PURE__ */ import_react20.default.createElement("label", { className: "text-xs text-slate-300" }, "Status da Quest"), /* @__PURE__ */ import_react20.default.createElement("div", { className: "relative" }, /* @__PURE__ */ import_react20.default.createElement(
11236
11482
  "button",
11237
11483
  {
11238
11484
  type: "button",
@@ -11418,7 +11664,7 @@ ${noteText}` : noteText : entry.notes,
11418
11664
  onClick: () => setIsReturnDropdownOpen(!isReturnDropdownOpen),
11419
11665
  className: "w-full bg-slate-800/80 p-2 text-xs rounded-lg border border-white/10 hover:border-white/20 focus:outline-none focus:ring-1 focus:ring-purple-400/50 transition-colors flex items-center justify-between"
11420
11666
  },
11421
- /* @__PURE__ */ import_react20.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react20.default.createElement(import_fi18.FiUser, { className: "text-slate-400", size: 12 }), /* @__PURE__ */ import_react20.default.createElement("span", { className: "text-slate-200 font-medium" }, ((_b = viewMembers.find((m) => String(m.id) === String(returnAssigneeId))) == null ? void 0 : _b.name) || "Selecionar respons\xE1vel...")),
11667
+ /* @__PURE__ */ import_react20.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react20.default.createElement(import_fi18.FiUser, { className: "text-slate-400", size: 12 }), /* @__PURE__ */ import_react20.default.createElement("span", { className: "text-slate-200 font-medium" }, ((_c = viewMembers.find((m) => String(m.id) === String(returnAssigneeId))) == null ? void 0 : _c.name) || "Selecionar respons\xE1vel...")),
11422
11668
  /* @__PURE__ */ import_react20.default.createElement(import_fi18.FiChevronDown, { className: `text-slate-400 transition-transform duration-200 ${isReturnDropdownOpen ? "rotate-180" : ""}`, size: 14 })
11423
11669
  ), isReturnDropdownOpen && /* @__PURE__ */ import_react20.default.createElement(import_react20.default.Fragment, null, /* @__PURE__ */ import_react20.default.createElement("div", { className: "fixed inset-0 z-40", onClick: () => {
11424
11670
  setIsReturnDropdownOpen(false);
@@ -13086,7 +13332,7 @@ function XViewScene({
13086
13332
  check_user_permission,
13087
13333
  get_view_members
13088
13334
  }) {
13089
- var _a, _b, _c, _d, _e, _f, _g, _h;
13335
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
13090
13336
  const { data: session, status } = (0, import_react29.useSession)();
13091
13337
  const router = (0, import_navigation.useRouter)();
13092
13338
  const searchParams = (0, import_navigation.useSearchParams)();
@@ -13915,20 +14161,23 @@ function XViewScene({
13915
14161
  sceneResponse.data.scene.nodes = [];
13916
14162
  sceneResponse.data.scene.links = [];
13917
14163
  }
13918
- sceneDataRef.current = sceneResponse.data.scene;
14164
+ sceneDataRef.current = {
14165
+ ...sceneResponse.data.scene,
14166
+ milestones: sceneResponse.data.scene.milestones || []
14167
+ };
13919
14168
  parentDataRef.current = sceneResponse.data.parent;
13920
14169
  ancestryDataRef.current = sceneResponse.data.ancestry;
13921
14170
  console.log(
13922
14171
  "Console de sceneResponse.data.scene:",
13923
- sceneResponse.data.scene
14172
+ sceneDataRef.current
13924
14173
  );
13925
14174
  console.log(
13926
14175
  "Console de sceneResponse.data.parent:",
13927
- sceneResponse.data.parent
14176
+ parentDataRef.current
13928
14177
  );
13929
14178
  console.log(
13930
14179
  "Console de sceneResponse.data.ancestry:",
13931
- sceneResponse.data.ancestry
14180
+ ancestryDataRef.current
13932
14181
  );
13933
14182
  setIsInitialized(true);
13934
14183
  } else {
@@ -15451,7 +15700,8 @@ function XViewScene({
15451
15700
  links: sceneDataRef2.current.links,
15452
15701
  quest_nodes: graphDataRef.current[sceneConfigId2].nodes,
15453
15702
  quest_links: graphDataRef.current[sceneConfigId2].links,
15454
- quest_counter: currentCounter + 1
15703
+ quest_counter: currentCounter + 1,
15704
+ milestones: sceneDataRef2.current.milestones || []
15455
15705
  };
15456
15706
  try {
15457
15707
  await actions.save_view_data(sceneSaveUrl2, sceneFileData);
@@ -17103,6 +17353,52 @@ function XViewScene({
17103
17353
  },
17104
17354
  [actionHandlerContext]
17105
17355
  );
17356
+ const handleUpdateMilestones = (0, import_react28.useCallback)(
17357
+ async (newMilestones) => {
17358
+ var _a2, _b2, _c2;
17359
+ if (!sceneDataRef.current) return;
17360
+ sceneDataRef.current.milestones = newMilestones;
17361
+ setSceneVersion((v) => v + 1);
17362
+ const isView = ((_a2 = viewParams == null ? void 0 : viewParams.type) == null ? void 0 : _a2.toLowerCase()) === "view";
17363
+ if (!isView || !save_view_data || !sceneSaveUrl) return;
17364
+ const { nodeObjects, allLinks } = stateRef.current;
17365
+ const currentNodes = Object.values(nodeObjects || {}).map((mesh) => {
17366
+ const {
17367
+ _baseEmissiveIntensity,
17368
+ labelObject,
17369
+ labelOffset,
17370
+ timelineIntervalBar,
17371
+ timelineEndLabel,
17372
+ ...serializableNodeData
17373
+ } = mesh.userData;
17374
+ return serializableNodeData;
17375
+ });
17376
+ const currentLinks = (allLinks || []).map((line) => {
17377
+ const { sourceNode, targetNode, ...serializableLinkData } = line.userData;
17378
+ return serializableLinkData;
17379
+ });
17380
+ const sceneFileData = {
17381
+ parent_dbs: sceneDataRef.current.parent_dbs,
17382
+ nodes: currentNodes,
17383
+ links: currentLinks,
17384
+ quest_nodes: ((_b2 = parentDataRef.current[sceneConfigId]) == null ? void 0 : _b2.nodes) || sceneDataRef.current.quest_nodes || [],
17385
+ quest_links: ((_c2 = parentDataRef.current[sceneConfigId]) == null ? void 0 : _c2.links) || sceneDataRef.current.quest_links || [],
17386
+ quest_counter: sceneDataRef.current.quest_counter || 1,
17387
+ milestones: newMilestones
17388
+ };
17389
+ try {
17390
+ await save_view_data(sceneSaveUrl, sceneFileData);
17391
+ } catch (error) {
17392
+ console.error("Erro ao salvar milestones:", error);
17393
+ }
17394
+ },
17395
+ [
17396
+ sceneConfigId,
17397
+ sceneSaveUrl,
17398
+ save_view_data,
17399
+ viewParams == null ? void 0 : viewParams.type
17400
+ ]
17401
+ );
17106
17402
  const handleSaveCurrentView = (0, import_react28.useCallback)(async () => {
17107
17403
  var _a2, _b2, _c2;
17108
17404
  const { nodeObjects, allLinks } = stateRef.current;
@@ -17137,7 +17433,8 @@ function XViewScene({
17137
17433
  links: currentLinks,
17138
17434
  quest_nodes: isView ? ((_b2 = parentDataRef.current[sceneConfigId]) == null ? void 0 : _b2.nodes) || [] : sceneDataRef.current.quest_nodes || [],
17139
17435
  quest_links: isView ? ((_c2 = parentDataRef.current[sceneConfigId]) == null ? void 0 : _c2.links) || [] : sceneDataRef.current.quest_links || [],
17140
- quest_counter: sceneDataRef.current.quest_counter || 1
17436
+ quest_counter: sceneDataRef.current.quest_counter || 1,
17437
+ milestones: sceneDataRef.current.milestones || []
17141
17438
  };
17142
17439
  try {
17143
17440
  await save_view_data(sceneSaveUrl, sceneFileData);
@@ -17474,7 +17771,9 @@ function XViewScene({
17474
17771
  isOpen: isSidebarOpen,
17475
17772
  onToggle: setIsSidebarOpen,
17476
17773
  userRole: userPermissionRole,
17477
- viewMembers
17774
+ viewMembers,
17775
+ milestones: ((_c = sceneDataRef.current) == null ? void 0 : _c.milestones) || [],
17776
+ onUpdateMilestones: handleUpdateMilestones
17478
17777
  }
17479
17778
  ),
17480
17779
  creationMode.isActive && /* @__PURE__ */ import_react28.default.createElement(
@@ -17498,8 +17797,8 @@ function XViewScene({
17498
17797
  },
17499
17798
  refEl: formRef,
17500
17799
  existingTypes: existingNodeTypes,
17501
- initialColor: (_c = creationMode.sourceNodeData) == null ? void 0 : _c.color,
17502
- sourceTypes: (_d = creationMode.sourceNodeData) == null ? void 0 : _d.type,
17800
+ initialColor: (_d = creationMode.sourceNodeData) == null ? void 0 : _d.color,
17801
+ sourceTypes: (_e = creationMode.sourceNodeData) == null ? void 0 : _e.type,
17503
17802
  onIntensityChange: handleGhostNodeIntensityChange,
17504
17803
  onUploadFile: upload_file_action,
17505
17804
  availableDatasets,
@@ -17528,8 +17827,8 @@ function XViewScene({
17528
17827
  transition: "opacity 200ms ease-out"
17529
17828
  },
17530
17829
  refEl: formRef,
17531
- fixedType: (_e = versionMode.sourceNodeData) == null ? void 0 : _e.type,
17532
- fixedColor: (_f = versionMode.sourceNodeData) == null ? void 0 : _f.color,
17830
+ fixedType: (_f = versionMode.sourceNodeData) == null ? void 0 : _f.type,
17831
+ fixedColor: (_g = versionMode.sourceNodeData) == null ? void 0 : _g.color,
17533
17832
  onUploadFile: upload_file_action,
17534
17833
  availableNodes: allAvailableNodes,
17535
17834
  availableAncestries: allAvailableAncestries
@@ -17557,8 +17856,10 @@ function XViewScene({
17557
17856
  availableNodes: allAvailableNodes,
17558
17857
  availableAncestries: allAvailableAncestries,
17559
17858
  viewName: viewParams == null ? void 0 : viewParams.name,
17560
- questCounter: ((_g = sceneDataRef.current) == null ? void 0 : _g.quest_counter) || 1,
17561
- viewMembers
17859
+ questCounter: ((_h = sceneDataRef.current) == null ? void 0 : _h.quest_counter) || 1,
17860
+ viewMembers,
17861
+ milestones: ((_i = sceneDataRef.current) == null ? void 0 : _i.milestones) || [],
17862
+ onUpdateMilestones: handleUpdateMilestones
17562
17863
  }
17563
17864
  ),
17564
17865
  readingMode.isActive && readingMode.ancestry && /* @__PURE__ */ import_react28.default.createElement(
@@ -17690,7 +17991,9 @@ function XViewScene({
17690
17991
  userRole: userPermissionRole,
17691
17992
  currentDatasetName: detailsNodeDatasetInfo == null ? void 0 : detailsNodeDatasetInfo.datasetName,
17692
17993
  viewMembers,
17693
- currentUser: session == null ? void 0 : session.user
17994
+ currentUser: session == null ? void 0 : session.user,
17995
+ milestones: ((_j = sceneDataRef.current) == null ? void 0 : _j.milestones) || [],
17996
+ onUpdateMilestones: handleUpdateMilestones
17694
17997
  }
17695
17998
  ),
17696
17999
  detailsNode && !detailsNode.is_quest && /* @__PURE__ */ import_react28.default.createElement(
@@ -17875,7 +18178,7 @@ function XViewScene({
17875
18178
  onClose: () => setIsImportModalOpen(false),
17876
18179
  onConfirm: handleConfirmImport,
17877
18180
  session,
17878
- parentDbs: ((_h = sceneDataRef.current) == null ? void 0 : _h.parent_dbs) || [],
18181
+ parentDbs: ((_k = sceneDataRef.current) == null ? void 0 : _k.parent_dbs) || [],
17879
18182
  onFetchAvailableFiles: import_parent_file_modal_get,
17880
18183
  currentViewName: viewParams == null ? void 0 : viewParams.name,
17881
18184
  currentAncestries: ancestryDataRef.current || []
package/dist/index.mjs CHANGED
@@ -586,12 +586,16 @@ function XViewSidebar({
586
586
  userRole,
587
587
  viewMembers = [],
588
588
  onActivateQuestLens = () => {
589
+ },
590
+ milestones = [],
591
+ onUpdateMilestones = () => {
589
592
  }
590
593
  }) {
591
594
  const [query, setQuery] = useState2("");
592
595
  const [showList, setShowList] = useState2(false);
593
596
  const [selectedNodeId, setSelectedNodeId] = useState2(null);
594
597
  const [isSaving, setIsSaving] = useState2(false);
598
+ const [newMilestoneName, setNewMilestoneName] = useState2("");
595
599
  const [activeFilters, setActiveFilters] = useState2([]);
596
600
  const [isFilterMenuOpen, setIsFilterMenuOpen] = useState2(false);
597
601
  const [filterCategory, setFilterCategory] = useState2("Type");
@@ -803,6 +807,28 @@ function XViewSidebar({
803
807
  setIsTypeDropdownOpen(false);
804
808
  }
805
809
  };
810
+ const handleAddMilestone = () => {
811
+ if (!newMilestoneName.trim()) return;
812
+ const newMilestone = {
813
+ id: `ms_${Date.now()}`,
814
+ name: newMilestoneName.trim(),
815
+ status: "active"
816
+ };
817
+ onUpdateMilestones([...milestones, newMilestone]);
818
+ setNewMilestoneName("");
819
+ };
820
+ const handleToggleMilestoneStatus = (id) => {
821
+ const updated = milestones.map(
822
+ (ms) => ms.id === id ? { ...ms, status: ms.status === "active" ? "inactive" : "active" } : ms
823
+ );
824
+ onUpdateMilestones(updated);
825
+ };
826
+ const handleRemoveMilestone = (id) => {
827
+ var _a;
828
+ if (!confirm(`Deseja remover o milestone "${(_a = milestones.find((m) => m.id === id)) == null ? void 0 : _a.name}"?`)) return;
829
+ const updated = milestones.filter((ms) => ms.id !== id);
830
+ onUpdateMilestones(updated);
831
+ };
806
832
  const ToggleButton = /* @__PURE__ */ React2.createElement(
807
833
  "button",
808
834
  {
@@ -1064,7 +1090,54 @@ function XViewSidebar({
1064
1090
  className: "flex-[2] py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-[11px] font-bold rounded-lg shadow-lg shadow-indigo-500/20 transition-all hover:scale-[1.02] active:scale-95"
1065
1091
  },
1066
1092
  "Aplicar Lente"
1067
- )))))), /* @__PURE__ */ React2.createElement("div", { className: "px-4 py-3 border-t border-white/10 bg-slate-900/30" }, /* @__PURE__ */ React2.createElement("section", { className: "space-y-2" }, /* @__PURE__ */ React2.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400" }, "Navega\xE7\xE3o")), ability.can("update", "Scene") && /* @__PURE__ */ React2.createElement(
1093
+ ))))), /* @__PURE__ */ React2.createElement("div", { className: "h-px bg-white/10" }), /* @__PURE__ */ React2.createElement("section", { className: "space-y-3" }, /* @__PURE__ */ React2.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400" }, "Milestones")), /* @__PURE__ */ React2.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React2.createElement(
1094
+ "input",
1095
+ {
1096
+ type: "text",
1097
+ value: newMilestoneName,
1098
+ onChange: (e) => setNewMilestoneName(e.target.value),
1099
+ onKeyDown: (e) => e.key === "Enter" && handleAddMilestone(),
1100
+ placeholder: "Novo Milestone...",
1101
+ className: "flex-1 bg-slate-900/50 border border-white/10 rounded-md px-2 py-1.5 text-xs text-slate-100 placeholder:text-slate-600 focus:outline-none focus:border-indigo-500/50"
1102
+ }
1103
+ ), /* @__PURE__ */ React2.createElement(
1104
+ "button",
1105
+ {
1106
+ onClick: handleAddMilestone,
1107
+ disabled: !newMilestoneName.trim(),
1108
+ className: "p-1.5 rounded-md bg-indigo-500/10 hover:bg-indigo-500/20 border border-indigo-500/30 text-indigo-300 disabled:opacity-30 transition-colors"
1109
+ },
1110
+ /* @__PURE__ */ React2.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React2.createElement("line", { x1: "12", y1: "5", x2: "12", y2: "19" }), /* @__PURE__ */ React2.createElement("line", { x1: "5", y1: "12", x2: "19", y2: "12" }))
1111
+ )), /* @__PURE__ */ React2.createElement("div", { className: "space-y-1.5 max-h-[200px] overflow-y-auto custom-scrollbar pr-1" }, milestones.length > 0 ? milestones.map((ms) => /* @__PURE__ */ React2.createElement(
1112
+ "div",
1113
+ {
1114
+ key: ms.id,
1115
+ className: `group flex items-center justify-between px-2.5 py-2 rounded-lg border transition-all ${ms.status === "active" ? "bg-indigo-500/5 border-indigo-500/20" : "bg-slate-900/30 border-white/5 opacity-60"}`
1116
+ },
1117
+ /* @__PURE__ */ React2.createElement("div", { className: "flex items-center gap-2 min-w-0" }, /* @__PURE__ */ React2.createElement(
1118
+ "div",
1119
+ {
1120
+ className: `w-1.5 h-1.5 rounded-full shrink-0 ${ms.status === "active" ? "bg-indigo-400 shadow-[0_0_8px_rgba(129,140,248,0.5)]" : "bg-slate-600"}`
1121
+ }
1122
+ ), /* @__PURE__ */ React2.createElement("span", { className: `text-[11px] truncate font-medium ${ms.status === "active" ? "text-slate-100" : "text-slate-500 line-through"}` }, ms.name)),
1123
+ /* @__PURE__ */ React2.createElement("div", { className: "flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity" }, /* @__PURE__ */ React2.createElement(
1124
+ "button",
1125
+ {
1126
+ onClick: () => handleToggleMilestoneStatus(ms.id),
1127
+ className: `p-1 rounded hover:bg-white/10 transition-colors ${ms.status === "active" ? "text-slate-400" : "text-indigo-400"}`,
1128
+ title: ms.status === "active" ? "Inativar" : "Ativar"
1129
+ },
1130
+ ms.status === "active" ? /* @__PURE__ */ React2.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React2.createElement("rect", { x: "6", y: "4", width: "4", height: "16" }), /* @__PURE__ */ React2.createElement("rect", { x: "14", y: "4", width: "4", height: "16" })) : /* @__PURE__ */ React2.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React2.createElement("polygon", { points: "5 3 19 12 5 21 5 3" }))
1131
+ ), /* @__PURE__ */ React2.createElement(
1132
+ "button",
1133
+ {
1134
+ onClick: () => handleRemoveMilestone(ms.id),
1135
+ className: "p-1 rounded hover:bg-red-500/20 text-slate-500 hover:text-red-400 transition-colors",
1136
+ title: "Remover"
1137
+ },
1138
+ /* @__PURE__ */ React2.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React2.createElement("polyline", { points: "3 6 5 6 21 6" }), /* @__PURE__ */ React2.createElement("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }), /* @__PURE__ */ React2.createElement("line", { x1: "10", y1: "11", x2: "10", y2: "17" }), /* @__PURE__ */ React2.createElement("line", { x1: "14", y1: "11", x2: "14", y2: "17" }))
1139
+ ))
1140
+ )) : /* @__PURE__ */ React2.createElement("p", { className: "text-[10px] text-slate-600 text-center py-2 italic font-light italic" }, "Nenhum milestone definido")))), /* @__PURE__ */ React2.createElement("div", { className: "px-4 py-3 border-t border-white/10 bg-slate-900/30" }, /* @__PURE__ */ React2.createElement("section", { className: "space-y-2" }, /* @__PURE__ */ React2.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2.createElement("p", { className: "text-[11px] uppercase tracking-wider text-slate-400" }, "Navega\xE7\xE3o")), ability.can("update", "Scene") && /* @__PURE__ */ React2.createElement(
1068
1141
  "button",
1069
1142
  {
1070
1143
  onClick: handleSaveClick,
@@ -2293,7 +2366,8 @@ var userActionHandlers = {
2293
2366
  nodes: sceneDataRef.current.nodes,
2294
2367
  links: sceneDataRef.current.links,
2295
2368
  quest_nodes: specificParentData.nodes,
2296
- quest_links: specificParentData.links
2369
+ quest_links: specificParentData.links,
2370
+ milestones: sceneDataRef.current.milestones || []
2297
2371
  };
2298
2372
  await context.actions.save_view_data(
2299
2373
  context.sceneSaveUrl,
@@ -2551,7 +2625,8 @@ var userActionHandlers = {
2551
2625
  nodes: sceneDataRef.current.nodes,
2552
2626
  links: sceneDataRef.current.links,
2553
2627
  quest_nodes: specificParentData.nodes,
2554
- quest_links: specificParentData.links
2628
+ quest_links: specificParentData.links,
2629
+ milestones: sceneDataRef.current.milestones || []
2555
2630
  };
2556
2631
  await context.actions.save_view_data(
2557
2632
  context.sceneSaveUrl,
@@ -2707,7 +2782,8 @@ var userActionHandlers = {
2707
2782
  nodes: sceneDataRef.current.nodes,
2708
2783
  links: sceneDataRef.current.links,
2709
2784
  quest_nodes: data.nodes,
2710
- quest_links: data.links
2785
+ quest_links: data.links,
2786
+ milestones: sceneDataRef.current.milestones || []
2711
2787
  };
2712
2788
  return context.actions.save_view_data(
2713
2789
  context.sceneSaveUrl,
@@ -3321,6 +3397,7 @@ var userActionHandlers = {
3321
3397
  links: sceneDataRef.current.links,
3322
3398
  quest_nodes: specificParentData.nodes,
3323
3399
  quest_links: specificParentData.links,
3400
+ milestones: sceneDataRef.current.milestones || [],
3324
3401
  quest_counter: sceneDataRef.current.quest_counter || 1
3325
3402
  };
3326
3403
  await context.actions.save_view_data(
@@ -3393,7 +3470,8 @@ var userActionHandlers = {
3393
3470
  links: sceneDataRef.current.links,
3394
3471
  quest_nodes: specificParentData.nodes,
3395
3472
  quest_links: specificParentData.links,
3396
- quest_counter: sceneDataRef.current.quest_counter || 1
3473
+ quest_counter: sceneDataRef.current.quest_counter || 1,
3474
+ milestones: sceneDataRef.current.milestones || []
3397
3475
  };
3398
3476
  await context.actions.save_view_data(
3399
3477
  context.sceneSaveUrl,
@@ -9487,11 +9565,15 @@ function InSceneQuestForm({
9487
9565
  questCounter = 1,
9488
9566
  // NOVA PROP
9489
9567
  viewMembers = [],
9490
- session = null
9568
+ session = null,
9569
+ milestones = [],
9570
+ onUpdateMilestones = () => {
9571
+ }
9491
9572
  }) {
9492
- var _a, _b;
9573
+ var _a, _b, _c;
9493
9574
  const [name, setName] = useState17("");
9494
9575
  const [assigneeId, setAssigneeId] = useState17("");
9576
+ const [milestoneId, setMilestoneId] = useState17("");
9495
9577
  const [types, setTypes] = useState17(["quest"]);
9496
9578
  const [typeInput, setTypeInput] = useState17("");
9497
9579
  const [status, setStatus] = useState17("Backlog");
@@ -9513,6 +9595,32 @@ function InSceneQuestForm({
9513
9595
  const [customProps, setCustomProps] = useState17([]);
9514
9596
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = useState17(false);
9515
9597
  const propsEndRef = useRef12(null);
9598
+ const [isMilestoneDropdownOpen, setIsMilestoneDropdownOpen] = useState17(false);
9599
+ const [milestoneSearchQuery, setMilestoneSearchQuery] = useState17("");
9600
+ const activeMilestones = React16.useMemo(
9601
+ () => (milestones || []).filter((ms) => ms.status === "active"),
9602
+ [milestones]
9603
+ );
9604
+ const filteredMilestones = React16.useMemo(() => {
9605
+ if (!milestoneSearchQuery.trim()) return activeMilestones;
9606
+ return activeMilestones.filter(
9607
+ (ms) => ms.name.toLowerCase().includes(milestoneSearchQuery.toLowerCase())
9608
+ );
9609
+ }, [activeMilestones, milestoneSearchQuery]);
9610
+ const handleSelectMilestone = (id) => {
9611
+ setMilestoneId(id);
9612
+ setIsMilestoneDropdownOpen(false);
9613
+ setMilestoneSearchQuery("");
9614
+ };
9615
+ const handleCreateMilestone = async (name2) => {
9616
+ if (!name2.trim()) return;
9617
+ const newId = `ms_${Date.now()}`;
9618
+ const newMs = { id: newId, name: name2.trim(), status: "active" };
9619
+ await onUpdateMilestones([...milestones, newMs]);
9620
+ setMilestoneId(newId);
9621
+ setIsMilestoneDropdownOpen(false);
9622
+ setMilestoneSearchQuery("");
9623
+ };
9516
9624
  const standardizedName = `${viewName} - ${questCounter} - \xBB ${name || "Nova Quest"}`;
9517
9625
  const handleAddProp = () => {
9518
9626
  const newProp = createNewCustomProperty(customProps);
@@ -9580,6 +9688,7 @@ function InSceneQuestForm({
9580
9688
  timeline: initialTimeline,
9581
9689
  Date: questDate ? { ...questDate, confirmed: true } : null,
9582
9690
  recurrence_config: recurrenceConfig,
9691
+ milestone_id: milestoneId || null,
9583
9692
  ...additionalData
9584
9693
  });
9585
9694
  };
@@ -9683,6 +9792,63 @@ function InSceneQuestForm({
9683
9792
  }
9684
9793
  ),
9685
9794
  s
9795
+ )))))), /* @__PURE__ */ React16.createElement("div", { className: "space-y-1.5 relative mt-2" }, /* @__PURE__ */ React16.createElement("label", { className: "text-xs text-slate-300 font-medium uppercase tracking-wider" }, "Milestone"), /* @__PURE__ */ React16.createElement("div", { className: "relative" }, /* @__PURE__ */ React16.createElement(
9796
+ "button",
9797
+ {
9798
+ type: "button",
9799
+ onClick: () => setIsMilestoneDropdownOpen(!isMilestoneDropdownOpen),
9800
+ className: "w-full bg-slate-800/70 p-2.5 text-sm rounded-lg border border-white/10 hover:border-white/20 focus:outline-none focus:ring-2 focus:ring-indigo-400/60 transition-colors flex items-center justify-between"
9801
+ },
9802
+ /* @__PURE__ */ React16.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React16.createElement(FiTarget, { className: "text-slate-400", size: 14 }), /* @__PURE__ */ React16.createElement("span", { className: "text-slate-200 font-medium" }, ((_a = (milestones || []).find((m) => m.id === milestoneId)) == null ? void 0 : _a.name) || "Nenhum")),
9803
+ /* @__PURE__ */ React16.createElement(
9804
+ FiChevronDown6,
9805
+ {
9806
+ className: `text-slate-400 transition-transform duration-200 ${isMilestoneDropdownOpen ? "rotate-180" : ""}`
9807
+ }
9808
+ )
9809
+ ), isMilestoneDropdownOpen && /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(
9810
+ "div",
9811
+ {
9812
+ className: "fixed inset-0 z-40",
9813
+ onClick: () => {
9814
+ setIsMilestoneDropdownOpen(false);
9815
+ setMilestoneSearchQuery("");
9816
+ }
9817
+ }
9818
+ ), /* @__PURE__ */ React16.createElement("div", { className: "absolute top-full left-0 mt-1.5 w-full bg-slate-900 border border-white/10 rounded-lg shadow-[0_8px_30px_rgba(0,0,0,0.5)] z-50 overflow-hidden flex flex-col" }, /* @__PURE__ */ React16.createElement("div", { className: "p-2 border-b border-white/5 bg-white/5 flex items-center gap-2" }, /* @__PURE__ */ React16.createElement(FiSearch5, { className: "text-slate-500", size: 14 }), /* @__PURE__ */ React16.createElement(
9819
+ "input",
9820
+ {
9821
+ type: "text",
9822
+ autoFocus: true,
9823
+ placeholder: "Buscar ou criar milestone...",
9824
+ value: milestoneSearchQuery,
9825
+ onChange: (e) => setMilestoneSearchQuery(e.target.value),
9826
+ className: "bg-transparent border-none outline-none text-xs text-white placeholder-slate-500 w-full",
9827
+ onClick: (e) => e.stopPropagation()
9828
+ }
9829
+ )), /* @__PURE__ */ React16.createElement("ul", { className: "max-h-48 overflow-y-auto custom-scrollbar" }, /* @__PURE__ */ React16.createElement(
9830
+ "li",
9831
+ {
9832
+ onClick: () => handleSelectMilestone(""),
9833
+ className: `px-3 py-2.5 text-sm cursor-pointer transition-colors flex items-center gap-2 ${!milestoneId ? "bg-indigo-500/20 text-white" : "text-slate-300 hover:bg-white/5 hover:text-white"}`
9834
+ },
9835
+ "Nenhum"
9836
+ ), filteredMilestones.map((ms) => /* @__PURE__ */ React16.createElement(
9837
+ "li",
9838
+ {
9839
+ key: ms.id,
9840
+ onClick: () => handleSelectMilestone(ms.id),
9841
+ className: `px-3 py-2.5 text-sm cursor-pointer transition-colors flex items-center gap-2 ${milestoneId === ms.id ? "bg-indigo-500/20 text-white" : "text-slate-300 hover:bg-white/5 hover:text-white"}`
9842
+ },
9843
+ ms.name
9844
+ )), milestoneSearchQuery.trim() && !filteredMilestones.some((ms) => ms.name.toLowerCase() === milestoneSearchQuery.toLowerCase().trim()) && /* @__PURE__ */ React16.createElement(
9845
+ "li",
9846
+ {
9847
+ onClick: () => handleCreateMilestone(milestoneSearchQuery),
9848
+ className: "px-3 py-3 text-sm cursor-pointer bg-indigo-600/20 text-indigo-200 hover:bg-indigo-600/40 transition-colors flex flex-col gap-0.5"
9849
+ },
9850
+ /* @__PURE__ */ React16.createElement("span", { className: "text-[10px] uppercase font-bold opacity-60" }, "Criar Novo Milestone"),
9851
+ /* @__PURE__ */ React16.createElement("span", { className: "font-medium" }, '"', milestoneSearchQuery, '"')
9686
9852
  )))))), /* @__PURE__ */ React16.createElement("div", { className: "space-y-1.5 relative mt-2" }, /* @__PURE__ */ React16.createElement("label", { className: "text-xs text-slate-300 font-medium uppercase tracking-wider" }, "Assignee (Respons\xE1vel)"), /* @__PURE__ */ React16.createElement("div", { className: "relative" }, /* @__PURE__ */ React16.createElement(
9687
9853
  "button",
9688
9854
  {
@@ -9690,7 +9856,7 @@ function InSceneQuestForm({
9690
9856
  onClick: () => setIsAssigneeDropdownOpen(!isAssigneeDropdownOpen),
9691
9857
  className: "w-full bg-slate-800/70 p-2.5 text-sm rounded-lg border border-white/10 hover:border-white/20 focus:outline-none focus:ring-2 focus:ring-indigo-400/60 transition-colors flex items-center justify-between"
9692
9858
  },
9693
- /* @__PURE__ */ React16.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React16.createElement(FiUser2, { className: "text-slate-400", size: 14 }), /* @__PURE__ */ React16.createElement("span", { className: "text-slate-200 font-medium" }, ((_a = viewMembers.find((m) => m.id === assigneeId)) == null ? void 0 : _a.name) || ((_b = viewMembers.find((m) => m.id === assigneeId)) == null ? void 0 : _b.email) || "Nenhum")),
9859
+ /* @__PURE__ */ React16.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React16.createElement(FiUser2, { className: "text-slate-400", size: 14 }), /* @__PURE__ */ React16.createElement("span", { className: "text-slate-200 font-medium" }, ((_b = viewMembers.find((m) => m.id === assigneeId)) == null ? void 0 : _b.name) || ((_c = viewMembers.find((m) => m.id === assigneeId)) == null ? void 0 : _c.email) || "Nenhum")),
9694
9860
  /* @__PURE__ */ React16.createElement(
9695
9861
  FiChevronDown6,
9696
9862
  {
@@ -10674,9 +10840,12 @@ function QuestDetailsPanel({
10674
10840
  userRole,
10675
10841
  currentDatasetName,
10676
10842
  viewMembers = [],
10677
- currentUser
10843
+ currentUser,
10844
+ milestones = [],
10845
+ onUpdateMilestones = () => {
10846
+ }
10678
10847
  }) {
10679
- var _a, _b;
10848
+ var _a, _b, _c;
10680
10849
  const initialRawTitle = (node == null ? void 0 : node.raw_title) || (((_a = node == null ? void 0 : node.name) == null ? void 0 : _a.includes(" - \xBB ")) ? node.name.split(" - \xBB ")[1] : node == null ? void 0 : node.name) || "";
10681
10850
  const [rawTitle, setRawTitle] = useState19(initialRawTitle);
10682
10851
  const prefixParts = ((node == null ? void 0 : node.name) || "").split(" - \xBB ");
@@ -10689,6 +10858,7 @@ function QuestDetailsPanel({
10689
10858
  const [assigneeId, setAssigneeId] = useState19((node == null ? void 0 : node.assignee_id) || "");
10690
10859
  const [description, setDescription] = useState19((node == null ? void 0 : node.description) ?? "");
10691
10860
  const [intensity, setIntensity] = useState19((node == null ? void 0 : node.intensity) !== void 0 ? node.intensity : 0);
10861
+ const [milestoneId, setMilestoneId] = useState19((node == null ? void 0 : node.milestone_id) || "");
10692
10862
  const [isStatusDropdownOpen, setIsStatusDropdownOpen] = useState19(false);
10693
10863
  const [isAssigneeDropdownOpen, setIsAssigneeDropdownOpen] = useState19(false);
10694
10864
  const [assigneeSearchQuery, setAssigneeSearchQuery] = useState19("");
@@ -10726,6 +10896,35 @@ function QuestDetailsPanel({
10726
10896
  minWidth: 320,
10727
10897
  maxWidth: maxPanelW
10728
10898
  });
10899
+ const [isMilestoneDropdownOpen, setIsMilestoneDropdownOpen] = useState19(false);
10900
+ const [milestoneSearchQuery, setMilestoneSearchQuery] = useState19("");
10901
+ const activeMilestones = React19.useMemo(
10902
+ () => (milestones || []).filter((ms) => ms.status === "active"),
10903
+ [milestones]
10904
+ );
10905
+ const filteredMilestones = React19.useMemo(() => {
10906
+ const search = milestoneSearchQuery.toLowerCase().trim();
10907
+ if (!search) return activeMilestones;
10908
+ return activeMilestones.filter(
10909
+ (ms) => ms.name.toLowerCase().includes(search)
10910
+ );
10911
+ }, [activeMilestones, milestoneSearchQuery]);
10912
+ const handleSelectMilestone = (id) => {
10913
+ setMilestoneId(id);
10914
+ setIsMilestoneDropdownOpen(false);
10915
+ setMilestoneSearchQuery("");
10916
+ setHasUnsavedChanges(true);
10917
+ };
10918
+ const handleCreateMilestone = async (name) => {
10919
+ if (!name.trim()) return;
10920
+ const newId = `ms_${Date.now()}`;
10921
+ const newMs = { id: newId, name: name.trim(), status: "active" };
10922
+ await onUpdateMilestones([...milestones, newMs]);
10923
+ setMilestoneId(newId);
10924
+ setIsMilestoneDropdownOpen(false);
10925
+ setMilestoneSearchQuery("");
10926
+ setHasUnsavedChanges(true);
10927
+ };
10729
10928
  useEffect16(() => {
10730
10929
  setWidth(isReadMode ? 700 : 440);
10731
10930
  }, [isReadMode, setWidth]);
@@ -10751,6 +10950,7 @@ function QuestDetailsPanel({
10751
10950
  setReturnAssigneeId("");
10752
10951
  setDescription((node == null ? void 0 : node.description) ?? "");
10753
10952
  setIntensity((node == null ? void 0 : node.intensity) !== void 0 ? node.intensity : 0);
10953
+ setMilestoneId((node == null ? void 0 : node.milestone_id) || "");
10754
10954
  setExistingSections((node == null ? void 0 : node.description_sections) || []);
10755
10955
  setCustomProps(extractCustomPropsFromNode(node || {}));
10756
10956
  setReviewStack((node == null ? void 0 : node.reviewStack) || []);
@@ -10763,6 +10963,7 @@ function QuestDetailsPanel({
10763
10963
  custom_date: "",
10764
10964
  next_assignee_id: ""
10765
10965
  });
10966
+ setMilestoneId((node == null ? void 0 : node.milestone_id) || "");
10766
10967
  setHasUnsavedChanges(false);
10767
10968
  }
10768
10969
  }, [node]);
@@ -11061,7 +11262,8 @@ ${noteText}` : noteText : entry.notes,
11061
11262
  reviewStack: currentReviewStack,
11062
11263
  timeline: currentTimeline,
11063
11264
  ...extrasObj,
11064
- version_node: node.version_node
11265
+ version_node: node.version_node,
11266
+ milestone_id: milestoneId || null
11065
11267
  };
11066
11268
  await onSave(dataToSave, keepOpen);
11067
11269
  onDataUpdate(dataToSave);
@@ -11263,7 +11465,51 @@ ${noteText}` : noteText : entry.notes,
11263
11465
  maxLength: "10",
11264
11466
  className: "flex-1 bg-slate-800/70 p-2 text-xs rounded-lg border border-white/10 focus:outline-none focus:ring-1 focus:ring-indigo-400/60"
11265
11467
  }
11266
- ))))), /* @__PURE__ */ React19.createElement("div", { className: "space-y-1.5 relative mt-2" }, /* @__PURE__ */ React19.createElement("label", { className: "text-xs text-slate-300" }, "Status da Quest"), /* @__PURE__ */ React19.createElement("div", { className: "relative" }, /* @__PURE__ */ React19.createElement(
11468
+ ))))), /* @__PURE__ */ React19.createElement("div", { className: "space-y-1.5 relative mt-2" }, /* @__PURE__ */ React19.createElement("label", { className: "text-xs text-slate-300" }, "Milestone"), /* @__PURE__ */ React19.createElement("div", { className: "relative" }, /* @__PURE__ */ React19.createElement(
11469
+ "button",
11470
+ {
11471
+ type: "button",
11472
+ onClick: () => canEdit && setIsMilestoneDropdownOpen(!isMilestoneDropdownOpen),
11473
+ disabled: !canEdit,
11474
+ className: `w-full bg-slate-800/70 p-2.5 text-sm rounded-lg border border-white/10 transition-colors flex items-center justify-between ${canEdit ? "hover:border-white/20 focus:ring-2 focus:ring-indigo-400/60 cursor-pointer" : "cursor-default opacity-80"}`
11475
+ },
11476
+ /* @__PURE__ */ React19.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React19.createElement(FiTarget3, { className: "text-slate-400", size: 14 }), /* @__PURE__ */ React19.createElement("span", { className: "text-slate-200 font-medium" }, ((_b = (milestones || []).find((m) => m.id === milestoneId)) == null ? void 0 : _b.name) || "Nenhum")),
11477
+ canEdit && /* @__PURE__ */ React19.createElement(FiChevronDown7, { className: `text-slate-400 transition-transform duration-200 ${isMilestoneDropdownOpen ? "rotate-180" : ""}` })
11478
+ ), isMilestoneDropdownOpen && canEdit && /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement("div", { className: "fixed inset-0 z-40", onClick: () => setIsMilestoneDropdownOpen(false) }), /* @__PURE__ */ React19.createElement("div", { className: "absolute top-full left-0 mt-1.5 w-full bg-slate-900 border border-white/10 rounded-lg shadow-[0_8px_30px_rgba(0,0,0,0.5)] z-50 overflow-hidden flex flex-col" }, /* @__PURE__ */ React19.createElement("div", { className: "p-2 border-b border-white/5 bg-white/5 flex items-center gap-2" }, /* @__PURE__ */ React19.createElement(FiSearch7, { className: "text-slate-500", size: 14 }), /* @__PURE__ */ React19.createElement(
11479
+ "input",
11480
+ {
11481
+ type: "text",
11482
+ autoFocus: true,
11483
+ placeholder: "Buscar ou criar milestone...",
11484
+ value: milestoneSearchQuery,
11485
+ onChange: (e) => setMilestoneSearchQuery(e.target.value),
11486
+ className: "bg-transparent border-none outline-none text-xs text-white placeholder-slate-500 w-full",
11487
+ onClick: (e) => e.stopPropagation()
11488
+ }
11489
+ )), /* @__PURE__ */ React19.createElement("ul", { className: "max-h-48 overflow-y-auto custom-scrollbar" }, /* @__PURE__ */ React19.createElement(
11490
+ "li",
11491
+ {
11492
+ onClick: () => handleSelectMilestone(""),
11493
+ className: `px-3 py-2.5 text-sm cursor-pointer transition-colors flex items-center gap-2 ${!milestoneId ? "bg-indigo-500/20 text-white" : "text-slate-300 hover:bg-white/5 hover:text-white"}`
11494
+ },
11495
+ "Nenhum"
11496
+ ), filteredMilestones.map((ms) => /* @__PURE__ */ React19.createElement(
11497
+ "li",
11498
+ {
11499
+ key: ms.id,
11500
+ onClick: () => handleSelectMilestone(ms.id),
11501
+ className: `px-3 py-2.5 text-sm cursor-pointer transition-colors flex items-center gap-2 ${milestoneId === ms.id ? "bg-indigo-500/20 text-white" : "text-slate-300 hover:bg-white/5 hover:text-white"}`
11502
+ },
11503
+ ms.name
11504
+ )), milestoneSearchQuery.trim() && !filteredMilestones.some((ms) => ms.name.toLowerCase() === milestoneSearchQuery.toLowerCase().trim()) && /* @__PURE__ */ React19.createElement(
11505
+ "li",
11506
+ {
11507
+ onClick: () => handleCreateMilestone(milestoneSearchQuery),
11508
+ className: "px-3 py-3 text-sm cursor-pointer bg-indigo-600/20 text-indigo-200 hover:bg-indigo-600/40 transition-colors flex flex-col gap-0.5"
11509
+ },
11510
+ /* @__PURE__ */ React19.createElement("span", { className: "text-[10px] uppercase font-bold opacity-60" }, "Criar Novo Milestone"),
11511
+ /* @__PURE__ */ React19.createElement("span", { className: "font-medium" }, '"', milestoneSearchQuery, '"')
11512
+ )))))), /* @__PURE__ */ React19.createElement("div", { className: "space-y-1.5 relative mt-2" }, /* @__PURE__ */ React19.createElement("label", { className: "text-xs text-slate-300" }, "Status da Quest"), /* @__PURE__ */ React19.createElement("div", { className: "relative" }, /* @__PURE__ */ React19.createElement(
11267
11513
  "button",
11268
11514
  {
11269
11515
  type: "button",
@@ -11449,7 +11695,7 @@ ${noteText}` : noteText : entry.notes,
11449
11695
  onClick: () => setIsReturnDropdownOpen(!isReturnDropdownOpen),
11450
11696
  className: "w-full bg-slate-800/80 p-2 text-xs rounded-lg border border-white/10 hover:border-white/20 focus:outline-none focus:ring-1 focus:ring-purple-400/50 transition-colors flex items-center justify-between"
11451
11697
  },
11452
- /* @__PURE__ */ React19.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React19.createElement(FiUser3, { className: "text-slate-400", size: 12 }), /* @__PURE__ */ React19.createElement("span", { className: "text-slate-200 font-medium" }, ((_b = viewMembers.find((m) => String(m.id) === String(returnAssigneeId))) == null ? void 0 : _b.name) || "Selecionar respons\xE1vel...")),
11698
+ /* @__PURE__ */ React19.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React19.createElement(FiUser3, { className: "text-slate-400", size: 12 }), /* @__PURE__ */ React19.createElement("span", { className: "text-slate-200 font-medium" }, ((_c = viewMembers.find((m) => String(m.id) === String(returnAssigneeId))) == null ? void 0 : _c.name) || "Selecionar respons\xE1vel...")),
11453
11699
  /* @__PURE__ */ React19.createElement(FiChevronDown7, { className: `text-slate-400 transition-transform duration-200 ${isReturnDropdownOpen ? "rotate-180" : ""}`, size: 14 })
11454
11700
  ), isReturnDropdownOpen && /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement("div", { className: "fixed inset-0 z-40", onClick: () => {
11455
11701
  setIsReturnDropdownOpen(false);
@@ -13130,7 +13376,7 @@ function XViewScene({
13130
13376
  check_user_permission,
13131
13377
  get_view_members
13132
13378
  }) {
13133
- var _a, _b, _c, _d, _e, _f, _g, _h;
13379
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
13134
13380
  const { data: session, status } = useSession();
13135
13381
  const router = useRouter();
13136
13382
  const searchParams = useSearchParams();
@@ -13959,20 +14205,23 @@ function XViewScene({
13959
14205
  sceneResponse.data.scene.nodes = [];
13960
14206
  sceneResponse.data.scene.links = [];
13961
14207
  }
13962
- sceneDataRef.current = sceneResponse.data.scene;
14208
+ sceneDataRef.current = {
14209
+ ...sceneResponse.data.scene,
14210
+ milestones: sceneResponse.data.scene.milestones || []
14211
+ };
13963
14212
  parentDataRef.current = sceneResponse.data.parent;
13964
14213
  ancestryDataRef.current = sceneResponse.data.ancestry;
13965
14214
  console.log(
13966
14215
  "Console de sceneResponse.data.scene:",
13967
- sceneResponse.data.scene
14216
+ sceneDataRef.current
13968
14217
  );
13969
14218
  console.log(
13970
14219
  "Console de sceneResponse.data.parent:",
13971
- sceneResponse.data.parent
14220
+ parentDataRef.current
13972
14221
  );
13973
14222
  console.log(
13974
14223
  "Console de sceneResponse.data.ancestry:",
13975
- sceneResponse.data.ancestry
14224
+ ancestryDataRef.current
13976
14225
  );
13977
14226
  setIsInitialized(true);
13978
14227
  } else {
@@ -15495,7 +15744,8 @@ function XViewScene({
15495
15744
  links: sceneDataRef2.current.links,
15496
15745
  quest_nodes: graphDataRef.current[sceneConfigId2].nodes,
15497
15746
  quest_links: graphDataRef.current[sceneConfigId2].links,
15498
- quest_counter: currentCounter + 1
15747
+ quest_counter: currentCounter + 1,
15748
+ milestones: sceneDataRef2.current.milestones || []
15499
15749
  };
15500
15750
  try {
15501
15751
  await actions.save_view_data(sceneSaveUrl2, sceneFileData);
@@ -17147,6 +17397,52 @@ function XViewScene({
17147
17397
  },
17148
17398
  [actionHandlerContext]
17149
17399
  );
17400
+ const handleUpdateMilestones = useCallback4(
17401
+ async (newMilestones) => {
17402
+ var _a2, _b2, _c2;
17403
+ if (!sceneDataRef.current) return;
17404
+ sceneDataRef.current.milestones = newMilestones;
17405
+ setSceneVersion((v) => v + 1);
17406
+ const isView = ((_a2 = viewParams == null ? void 0 : viewParams.type) == null ? void 0 : _a2.toLowerCase()) === "view";
17407
+ if (!isView || !save_view_data || !sceneSaveUrl) return;
17408
+ const { nodeObjects, allLinks } = stateRef.current;
17409
+ const currentNodes = Object.values(nodeObjects || {}).map((mesh) => {
17410
+ const {
17411
+ _baseEmissiveIntensity,
17412
+ labelObject,
17413
+ labelOffset,
17414
+ timelineIntervalBar,
17415
+ timelineEndLabel,
17416
+ ...serializableNodeData
17417
+ } = mesh.userData;
17418
+ return serializableNodeData;
17419
+ });
17420
+ const currentLinks = (allLinks || []).map((line) => {
17421
+ const { sourceNode, targetNode, ...serializableLinkData } = line.userData;
17422
+ return serializableLinkData;
17423
+ });
17424
+ const sceneFileData = {
17425
+ parent_dbs: sceneDataRef.current.parent_dbs,
17426
+ nodes: currentNodes,
17427
+ links: currentLinks,
17428
+ quest_nodes: ((_b2 = parentDataRef.current[sceneConfigId]) == null ? void 0 : _b2.nodes) || sceneDataRef.current.quest_nodes || [],
17429
+ quest_links: ((_c2 = parentDataRef.current[sceneConfigId]) == null ? void 0 : _c2.links) || sceneDataRef.current.quest_links || [],
17430
+ quest_counter: sceneDataRef.current.quest_counter || 1,
17431
+ milestones: newMilestones
17432
+ };
17433
+ try {
17434
+ await save_view_data(sceneSaveUrl, sceneFileData);
17435
+ } catch (error) {
17436
+ console.error("Erro ao salvar milestones:", error);
17437
+ }
17438
+ },
17439
+ [
17440
+ sceneConfigId,
17441
+ sceneSaveUrl,
17442
+ save_view_data,
17443
+ viewParams == null ? void 0 : viewParams.type
17444
+ ]
17445
+ );
17150
17446
  const handleSaveCurrentView = useCallback4(async () => {
17151
17447
  var _a2, _b2, _c2;
17152
17448
  const { nodeObjects, allLinks } = stateRef.current;
@@ -17181,7 +17477,8 @@ function XViewScene({
17181
17477
  links: currentLinks,
17182
17478
  quest_nodes: isView ? ((_b2 = parentDataRef.current[sceneConfigId]) == null ? void 0 : _b2.nodes) || [] : sceneDataRef.current.quest_nodes || [],
17183
17479
  quest_links: isView ? ((_c2 = parentDataRef.current[sceneConfigId]) == null ? void 0 : _c2.links) || [] : sceneDataRef.current.quest_links || [],
17184
- quest_counter: sceneDataRef.current.quest_counter || 1
17480
+ quest_counter: sceneDataRef.current.quest_counter || 1,
17481
+ milestones: sceneDataRef.current.milestones || []
17185
17482
  };
17186
17483
  try {
17187
17484
  await save_view_data(sceneSaveUrl, sceneFileData);
@@ -17518,7 +17815,9 @@ function XViewScene({
17518
17815
  isOpen: isSidebarOpen,
17519
17816
  onToggle: setIsSidebarOpen,
17520
17817
  userRole: userPermissionRole,
17521
- viewMembers
17818
+ viewMembers,
17819
+ milestones: ((_c = sceneDataRef.current) == null ? void 0 : _c.milestones) || [],
17820
+ onUpdateMilestones: handleUpdateMilestones
17522
17821
  }
17523
17822
  ),
17524
17823
  creationMode.isActive && /* @__PURE__ */ React27.createElement(
@@ -17542,8 +17841,8 @@ function XViewScene({
17542
17841
  },
17543
17842
  refEl: formRef,
17544
17843
  existingTypes: existingNodeTypes,
17545
- initialColor: (_c = creationMode.sourceNodeData) == null ? void 0 : _c.color,
17546
- sourceTypes: (_d = creationMode.sourceNodeData) == null ? void 0 : _d.type,
17844
+ initialColor: (_d = creationMode.sourceNodeData) == null ? void 0 : _d.color,
17845
+ sourceTypes: (_e = creationMode.sourceNodeData) == null ? void 0 : _e.type,
17547
17846
  onIntensityChange: handleGhostNodeIntensityChange,
17548
17847
  onUploadFile: upload_file_action,
17549
17848
  availableDatasets,
@@ -17572,8 +17871,8 @@ function XViewScene({
17572
17871
  transition: "opacity 200ms ease-out"
17573
17872
  },
17574
17873
  refEl: formRef,
17575
- fixedType: (_e = versionMode.sourceNodeData) == null ? void 0 : _e.type,
17576
- fixedColor: (_f = versionMode.sourceNodeData) == null ? void 0 : _f.color,
17874
+ fixedType: (_f = versionMode.sourceNodeData) == null ? void 0 : _f.type,
17875
+ fixedColor: (_g = versionMode.sourceNodeData) == null ? void 0 : _g.color,
17577
17876
  onUploadFile: upload_file_action,
17578
17877
  availableNodes: allAvailableNodes,
17579
17878
  availableAncestries: allAvailableAncestries
@@ -17601,8 +17900,10 @@ function XViewScene({
17601
17900
  availableNodes: allAvailableNodes,
17602
17901
  availableAncestries: allAvailableAncestries,
17603
17902
  viewName: viewParams == null ? void 0 : viewParams.name,
17604
- questCounter: ((_g = sceneDataRef.current) == null ? void 0 : _g.quest_counter) || 1,
17605
- viewMembers
17903
+ questCounter: ((_h = sceneDataRef.current) == null ? void 0 : _h.quest_counter) || 1,
17904
+ viewMembers,
17905
+ milestones: ((_i = sceneDataRef.current) == null ? void 0 : _i.milestones) || [],
17906
+ onUpdateMilestones: handleUpdateMilestones
17606
17907
  }
17607
17908
  ),
17608
17909
  readingMode.isActive && readingMode.ancestry && /* @__PURE__ */ React27.createElement(
@@ -17734,7 +18035,9 @@ function XViewScene({
17734
18035
  userRole: userPermissionRole,
17735
18036
  currentDatasetName: detailsNodeDatasetInfo == null ? void 0 : detailsNodeDatasetInfo.datasetName,
17736
18037
  viewMembers,
17737
- currentUser: session == null ? void 0 : session.user
18038
+ currentUser: session == null ? void 0 : session.user,
18039
+ milestones: ((_j = sceneDataRef.current) == null ? void 0 : _j.milestones) || [],
18040
+ onUpdateMilestones: handleUpdateMilestones
17738
18041
  }
17739
18042
  ),
17740
18043
  detailsNode && !detailsNode.is_quest && /* @__PURE__ */ React27.createElement(
@@ -17919,7 +18222,7 @@ function XViewScene({
17919
18222
  onClose: () => setIsImportModalOpen(false),
17920
18223
  onConfirm: handleConfirmImport,
17921
18224
  session,
17922
- parentDbs: ((_h = sceneDataRef.current) == null ? void 0 : _h.parent_dbs) || [],
18225
+ parentDbs: ((_k = sceneDataRef.current) == null ? void 0 : _k.parent_dbs) || [],
17923
18226
  onFetchAvailableFiles: import_parent_file_modal_get,
17924
18227
  currentViewName: viewParams == null ? void 0 : viewParams.name,
17925
18228
  currentAncestries: ancestryDataRef.current || []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lv-x-software-house/x_view",
3
- "version": "1.2.5-dev.46",
3
+ "version": "1.2.5-dev.47",
4
4
  "description": "Pacote privado contendo os componentes e lógica de renderização 3D do X View.",
5
5
  "author": "iv.x - Engenharia de Software - ivxsoftwarehouse@gmail.com",
6
6
  "license": "UNLICENSED",