@lv-x-software-house/x_view 1.2.5-dev.34 → 1.2.5-dev.36

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 +172 -25
  2. package/dist/index.mjs +172 -25
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -622,8 +622,10 @@ function XViewSidebar({
622
622
  },
623
623
  isOpen,
624
624
  onToggle,
625
- userRole
626
- // 2. Receber a prop userRole
625
+ userRole,
626
+ viewMembers = [],
627
+ onActivateQuestLens = () => {
628
+ }
627
629
  }) {
628
630
  const [query, setQuery] = (0, import_react2.useState)("");
629
631
  const [showList, setShowList] = (0, import_react2.useState)(false);
@@ -633,12 +635,16 @@ function XViewSidebar({
633
635
  const [isFilterMenuOpen, setIsFilterMenuOpen] = (0, import_react2.useState)(false);
634
636
  const [filterCategory, setFilterCategory] = (0, import_react2.useState)("Type");
635
637
  const [filterValue, setFilterValue] = (0, import_react2.useState)("");
638
+ const [questFilterAssignee, setQuestFilterAssignee] = (0, import_react2.useState)("");
639
+ const [questFilterStatus, setQuestFilterStatus] = (0, import_react2.useState)("");
640
+ const [isQuestLensMenuOpen, setIsQuestLensMenuOpen] = (0, import_react2.useState)(false);
636
641
  const [isTypeDropdownOpen, setIsTypeDropdownOpen] = (0, import_react2.useState)(false);
637
642
  const [highlightedTypeIndex, setHighlightedTypeIndex] = (0, import_react2.useState)(-1);
638
643
  const containerRef = (0, import_react2.useRef)(null);
639
644
  const inputRef = (0, import_react2.useRef)(null);
640
645
  const filterMenuRef = (0, import_react2.useRef)(null);
641
646
  const typeDropdownRef = (0, import_react2.useRef)(null);
647
+ const questLensRef = (0, import_react2.useRef)(null);
642
648
  const ability = (0, import_react2.useMemo)(() => {
643
649
  return defineAbilityFor(userRole);
644
650
  }, [userRole]);
@@ -743,6 +749,17 @@ function XViewSidebar({
743
749
  }
744
750
  return () => document.removeEventListener("mousedown", handleClickOutside);
745
751
  }, [isFilterMenuOpen, isTypeDropdownOpen]);
752
+ (0, import_react2.useEffect)(() => {
753
+ const handleClickOutsideQuest = (event) => {
754
+ if (questLensRef.current && !questLensRef.current.contains(event.target)) {
755
+ setIsQuestLensMenuOpen(false);
756
+ }
757
+ };
758
+ if (isQuestLensMenuOpen) {
759
+ document.addEventListener("mousedown", handleClickOutsideQuest);
760
+ }
761
+ return () => document.removeEventListener("mousedown", handleClickOutsideQuest);
762
+ }, [isQuestLensMenuOpen]);
746
763
  (0, import_react2.useEffect)(() => {
747
764
  if (!query) setSelectedNodeId(null);
748
765
  }, [query]);
@@ -1034,7 +1051,58 @@ function XViewSidebar({
1034
1051
  },
1035
1052
  /* @__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("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }), /* @__PURE__ */ import_react2.default.createElement("polyline", { points: "2 17 12 22 22 17" }), /* @__PURE__ */ import_react2.default.createElement("polyline", { points: "2 12 12 17 22 12" })),
1036
1053
  "Ancestry Board"
1037
- ))), /* @__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(
1054
+ ), /* @__PURE__ */ import_react2.default.createElement("div", { className: "relative", ref: questLensRef }, /* @__PURE__ */ import_react2.default.createElement(
1055
+ "button",
1056
+ {
1057
+ onClick: () => setIsQuestLensMenuOpen(!isQuestLensMenuOpen),
1058
+ className: `${buttonBaseClass} ${isQuestLensMenuOpen || questFilterAssignee || questFilterStatus ? "bg-indigo-500/30 border-indigo-400" : ""}`,
1059
+ title: "Abrir filtros da Lente de Quests"
1060
+ },
1061
+ /* @__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("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M9 12l2 2 4-4" })),
1062
+ "Quest Lens",
1063
+ (questFilterAssignee || questFilterStatus) && /* @__PURE__ */ import_react2.default.createElement("span", { className: "ml-1 px-1.5 py-0.5 rounded-full bg-indigo-500 text-[9px] font-bold" }, "!")
1064
+ ), isQuestLensMenuOpen && /* @__PURE__ */ import_react2.default.createElement("div", { className: "absolute left-0 bottom-full mb-2 w-full p-4 rounded-xl bg-slate-900 border border-white/10 shadow-2xl z-50 flex flex-col gap-4 animate-in fade-in slide-in-from-bottom-2 duration-200" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ import_react2.default.createElement("label", { className: "text-[10px] uppercase text-slate-400 font-bold tracking-widest" }, "Respons\xE1vel"), /* @__PURE__ */ import_react2.default.createElement(
1065
+ "select",
1066
+ {
1067
+ value: questFilterAssignee,
1068
+ onChange: (e) => setQuestFilterAssignee(e.target.value),
1069
+ className: "w-full bg-slate-800 p-2 text-xs rounded border border-white/10 focus:outline-none focus:border-indigo-500/50 text-white appearance-none cursor-pointer"
1070
+ },
1071
+ /* @__PURE__ */ import_react2.default.createElement("option", { value: "" }, "Todos os Respons\xE1veis"),
1072
+ viewMembers.map((m) => /* @__PURE__ */ import_react2.default.createElement("option", { key: m.id, value: m.id }, m.name || m.email))
1073
+ )), /* @__PURE__ */ import_react2.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ import_react2.default.createElement("label", { className: "text-[10px] uppercase text-slate-400 font-bold tracking-widest" }, "Status"), /* @__PURE__ */ import_react2.default.createElement(
1074
+ "select",
1075
+ {
1076
+ value: questFilterStatus,
1077
+ onChange: (e) => setQuestFilterStatus(e.target.value),
1078
+ className: "w-full bg-slate-800 p-2 text-xs rounded border border-white/10 focus:outline-none focus:border-indigo-500/50 text-white appearance-none cursor-pointer"
1079
+ },
1080
+ /* @__PURE__ */ import_react2.default.createElement("option", { value: "" }, "Todos (Exclui Done)"),
1081
+ /* @__PURE__ */ import_react2.default.createElement("option", { value: "Backlog" }, "Backlog"),
1082
+ /* @__PURE__ */ import_react2.default.createElement("option", { value: "In Progress" }, "In Progress"),
1083
+ /* @__PURE__ */ import_react2.default.createElement("option", { value: "Review" }, "Review"),
1084
+ /* @__PURE__ */ import_react2.default.createElement("option", { value: "Done" }, "Done")
1085
+ )), /* @__PURE__ */ import_react2.default.createElement("div", { className: "flex gap-2 pt-1" }, /* @__PURE__ */ import_react2.default.createElement(
1086
+ "button",
1087
+ {
1088
+ onClick: () => {
1089
+ setQuestFilterAssignee("");
1090
+ setQuestFilterStatus("");
1091
+ },
1092
+ className: "flex-1 py-2 bg-white/5 hover:bg-white/10 text-slate-300 text-[11px] font-medium rounded-lg transition-colors border border-white/5"
1093
+ },
1094
+ "Limpar"
1095
+ ), /* @__PURE__ */ import_react2.default.createElement(
1096
+ "button",
1097
+ {
1098
+ onClick: () => {
1099
+ onActivateQuestLens({ assigneeId: questFilterAssignee, status: questFilterStatus });
1100
+ setIsQuestLensMenuOpen(false);
1101
+ },
1102
+ 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"
1103
+ },
1104
+ "Aplicar Lente"
1105
+ )))))), /* @__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(
1038
1106
  "button",
1039
1107
  {
1040
1108
  onClick: handleSaveClick,
@@ -9920,7 +9988,8 @@ function QuestDetailsPanel({
9920
9988
  const newEvent = createTimelineEntry(
9921
9989
  actorId,
9922
9990
  `Status "In Progress" - Respons\xE1vel: ${targetName}`,
9923
- "status_changed"
9991
+ "status_changed",
9992
+ { metadata: { to_assignee_id: returnAssigneeId } }
9924
9993
  );
9925
9994
  const updatedTimeline = [...timeline, newEvent];
9926
9995
  const updatedStatus = "In Progress";
@@ -10040,7 +10109,8 @@ ${noteText}` : noteText : entry.notes,
10040
10109
  const newEvent = createTimelineEntry(
10041
10110
  actorId,
10042
10111
  `Status "${newStatus}" - Respons\xE1vel: ${assigneeName}`,
10043
- "status_changed"
10112
+ "status_changed",
10113
+ { metadata: { to_assignee_id: assigneeId } }
10044
10114
  );
10045
10115
  const newTimeline = [...timeline, newEvent];
10046
10116
  setTimeline(newTimeline);
@@ -11945,6 +12015,9 @@ var TIMELINE_NODE_SPACING_X = 20;
11945
12015
  var TIMELINE_INTERVAL_BAR_RADIUS = 0.2;
11946
12016
  var TIMELINE_INTERVAL_BAR_OPACITY = 0.85;
11947
12017
  var TIMELINE_GOLD_COLOR = 16766720;
12018
+ var QUEST_LENS_COLUMN_SPACING = 80;
12019
+ var QUEST_LENS_ROW_SPACING = 40;
12020
+ var QUEST_STATUS_ORDER = ["Backlog", "In Progress", "Review", "Done"];
11948
12021
  var getAllNodeIdsFromTree = (treeNode) => {
11949
12022
  const ids = /* @__PURE__ */ new Set();
11950
12023
  function traverse(node) {
@@ -12644,6 +12717,82 @@ function XViewScene({
12644
12717
  }
12645
12718
  });
12646
12719
  }, []);
12720
+ const handleActivateQuestLens = (0, import_react28.useCallback)(({ assigneeId, status: status2 }) => {
12721
+ var _a2;
12722
+ const { nodeObjects, tweenGroup, graphGroup } = stateRef.current;
12723
+ if (!nodeObjects || !tweenGroup || !graphGroup) return;
12724
+ const allQuests = ((_a2 = sceneDataRef.current) == null ? void 0 : _a2.quest_nodes) || [];
12725
+ const filteredQuests = allQuests.filter((q) => {
12726
+ const matchAssignee = !assigneeId || String(q.assignee_id) === String(assigneeId);
12727
+ const matchStatus = !status2 ? q.status !== "Done" : String(q.status) === String(status2);
12728
+ return matchAssignee && matchStatus;
12729
+ });
12730
+ if (filteredQuests.length === 0) {
12731
+ console.warn("Nenhuma quest encontrada para os filtros selecionados.");
12732
+ return;
12733
+ }
12734
+ const questsByStatus = {
12735
+ Backlog: [],
12736
+ "In Progress": [],
12737
+ Review: [],
12738
+ Done: []
12739
+ };
12740
+ filteredQuests.forEach((q) => {
12741
+ if (questsByStatus[q.status]) {
12742
+ questsByStatus[q.status].push(q);
12743
+ }
12744
+ });
12745
+ const startX = -((QUEST_STATUS_ORDER.length - 1) * QUEST_LENS_COLUMN_SPACING) / 2;
12746
+ Object.keys(nodeObjects).forEach((id) => {
12747
+ const mesh = nodeObjects[id];
12748
+ const isFilteredQuest = filteredQuests.some(
12749
+ (q) => String(q.id) === String(id)
12750
+ );
12751
+ if (!isFilteredQuest) {
12752
+ if (mesh.material) {
12753
+ const fadeTween = new import_tween2.Tween(mesh.material).to({ opacity: 0.15 }, 1200).easing(import_tween2.Easing.Cubic.Out);
12754
+ tweenGroup.add(fadeTween);
12755
+ fadeTween.start();
12756
+ }
12757
+ if (mesh.userData.labelObject) {
12758
+ mesh.userData.labelObject.visible = false;
12759
+ }
12760
+ } else {
12761
+ if (mesh.material) {
12762
+ const fadeInTween = new import_tween2.Tween(mesh.material).to({ opacity: 1 }, 1200).easing(import_tween2.Easing.Cubic.Out);
12763
+ tweenGroup.add(fadeInTween);
12764
+ fadeInTween.start();
12765
+ }
12766
+ if (mesh.userData.labelObject) {
12767
+ mesh.userData.labelObject.visible = true;
12768
+ }
12769
+ }
12770
+ });
12771
+ QUEST_STATUS_ORDER.forEach((statusName, colIndex) => {
12772
+ if (status2 && status2 !== statusName) return;
12773
+ const questsInCol = questsByStatus[statusName];
12774
+ const colX = startX + colIndex * QUEST_LENS_COLUMN_SPACING;
12775
+ questsInCol.forEach((q, rowIndex) => {
12776
+ const nodeId = String(q.id);
12777
+ let mesh = nodeObjects[nodeId];
12778
+ if (!mesh) {
12779
+ mesh = addOrUpdateNodeMesh(q, new THREE3.Vector3(colX, 150, 0), true);
12780
+ }
12781
+ const targetY = (questsInCol.length - 1) * 0.5 * QUEST_LENS_ROW_SPACING - rowIndex * QUEST_LENS_ROW_SPACING;
12782
+ const lensTween = new import_tween2.Tween(mesh.position).to({ x: colX, y: targetY, z: 0 }, 1500).easing(import_tween2.Easing.Cubic.InOut);
12783
+ tweenGroup.add(lensTween);
12784
+ lensTween.start();
12785
+ if (mesh.userData.labelObject) {
12786
+ const labelTargetPos = new THREE3.Vector3(colX, targetY, 0).add(
12787
+ mesh.userData.labelOffset || new THREE3.Vector3(0, -2.5, 0)
12788
+ );
12789
+ const labelTween = new import_tween2.Tween(mesh.userData.labelObject.position).to(labelTargetPos, 1500).easing(import_tween2.Easing.Cubic.InOut);
12790
+ tweenGroup.add(labelTween);
12791
+ labelTween.start();
12792
+ }
12793
+ });
12794
+ });
12795
+ }, [addOrUpdateNodeMesh]);
12647
12796
  const handleVersionTimeline = (0, import_react28.useCallback)((sourceMesh, versionMeshes) => {
12648
12797
  const { tweenGroup, timelineIntervalsGroup } = stateRef.current;
12649
12798
  if (!tweenGroup || !timelineIntervalsGroup || versionMeshes.length === 0)
@@ -14023,7 +14172,7 @@ function XViewScene({
14023
14172
  setDetailsNode(updatedNode);
14024
14173
  }, [session, actionHandlerContext]);
14025
14174
  const handleFinishQuestQuick = (0, import_react28.useCallback)((questNode) => {
14026
- var _a2, _b2, _c2, _d2, _e2, _f2;
14175
+ var _a2, _b2, _c2, _d2;
14027
14176
  if (!questNode || !actionHandlerContext) return;
14028
14177
  const {
14029
14178
  labelObject,
@@ -14043,18 +14192,8 @@ function XViewScene({
14043
14192
  if (currentReviewerIdx !== -1 && reviewStack[currentReviewerIdx].userId === cleanQuestNode.assignee_id) {
14044
14193
  reviewStack[currentReviewerIdx].status = "completed";
14045
14194
  nextIdxToAssign = currentReviewerIdx + 1;
14046
- newTimeline.push(createTimelineEntry(
14047
- ((_a2 = session == null ? void 0 : session.user) == null ? void 0 : _a2.id) || cleanQuestNode.assignee_id,
14048
- `Status "Review" - Conclu\xEDda por ${((_b2 = viewMembers.find((m) => m.id === cleanQuestNode.assignee_id)) == null ? void 0 : _b2.name) || "revisor"}`,
14049
- "status_changed"
14050
- ));
14051
14195
  } else {
14052
14196
  nextIdxToAssign = currentReviewerIdx !== -1 ? currentReviewerIdx : reviewStack.length;
14053
- newTimeline.push(createTimelineEntry(
14054
- ((_c2 = session == null ? void 0 : session.user) == null ? void 0 : _c2.id) || cleanQuestNode.assignee_id,
14055
- `Status "Done" - Finalizado por ${((_d2 = viewMembers.find((m) => m.id === cleanQuestNode.assignee_id)) == null ? void 0 : _d2.name) || ((_e2 = session == null ? void 0 : session.user) == null ? void 0 : _e2.name) || "respons\xE1vel"}`,
14056
- "status_changed"
14057
- ));
14058
14197
  }
14059
14198
  if (nextIdxToAssign < reviewStack.length) {
14060
14199
  const remainingMandatory = reviewStack.slice(nextIdxToAssign).some((r) => r.mandatory);
@@ -14063,12 +14202,6 @@ function XViewScene({
14063
14202
  const nextReviewer = reviewStack[nextIdxToAssign];
14064
14203
  nextAssigneeId = nextReviewer.userId;
14065
14204
  nextStatus = "Review";
14066
- newTimeline.push(createTimelineEntry(
14067
- "system",
14068
- `Status "Review" - Respons\xE1vel: ${((_f2 = viewMembers.find((m) => m.id === nextAssigneeId)) == null ? void 0 : _f2.name) || "revisor"}`,
14069
- "status_changed",
14070
- { metadata: { to_assignee_id: nextAssigneeId } }
14071
- ));
14072
14205
  } else {
14073
14206
  nextStatus = "Done";
14074
14207
  for (let i = nextIdxToAssign; i < reviewStack.length; i++) {
@@ -14078,10 +14211,22 @@ function XViewScene({
14078
14211
  } else {
14079
14212
  nextStatus = "Done";
14080
14213
  }
14081
- if (nextStatus === "Done") {
14214
+ if (nextStatus === "Review") {
14215
+ const nextReviewerName = ((_a2 = viewMembers.find((m) => m.id === nextAssigneeId)) == null ? void 0 : _a2.name) || "revisor";
14082
14216
  newTimeline.push(createTimelineEntry(
14083
14217
  "system",
14084
- `Status "Done" - Quest Finalizada`,
14218
+ `Status "Review" - Respons\xE1vel: ${nextReviewerName}`,
14219
+ "status_changed",
14220
+ { metadata: { to_assignee_id: nextAssigneeId } }
14221
+ ));
14222
+ } else if (nextStatus === "Done") {
14223
+ const finisherName = ((_b2 = viewMembers.find((m) => {
14224
+ var _a3;
14225
+ return m.id === (((_a3 = session == null ? void 0 : session.user) == null ? void 0 : _a3.id) || cleanQuestNode.assignee_id);
14226
+ })) == null ? void 0 : _b2.name) || ((_c2 = session == null ? void 0 : session.user) == null ? void 0 : _c2.name) || "respons\xE1vel";
14227
+ newTimeline.push(createTimelineEntry(
14228
+ ((_d2 = session == null ? void 0 : session.user) == null ? void 0 : _d2.id) || cleanQuestNode.assignee_id,
14229
+ `Status "Done" - Finalizado por ${finisherName}`,
14085
14230
  "status_changed"
14086
14231
  ));
14087
14232
  }
@@ -16168,13 +16313,15 @@ function XViewScene({
16168
16313
  onOpenImportModal: () => setIsImportModalOpen(true),
16169
16314
  importSuccessMessage,
16170
16315
  onActivateTimeline: handleActivateTimeline,
16316
+ onActivateQuestLens: handleActivateQuestLens,
16171
16317
  onOpenAncestryBoard: handleOpenAncestryBoard,
16172
16318
  viewType: viewParams == null ? void 0 : viewParams.type,
16173
16319
  viewName: viewParams == null ? void 0 : viewParams.name,
16174
16320
  onNavigateBack: handleNavigateBack,
16175
16321
  isOpen: isSidebarOpen,
16176
16322
  onToggle: setIsSidebarOpen,
16177
- userRole: userPermissionRole
16323
+ userRole: userPermissionRole,
16324
+ viewMembers
16178
16325
  }
16179
16326
  ),
16180
16327
  creationMode.isActive && /* @__PURE__ */ import_react28.default.createElement(
package/dist/index.mjs CHANGED
@@ -583,8 +583,10 @@ function XViewSidebar({
583
583
  },
584
584
  isOpen,
585
585
  onToggle,
586
- userRole
587
- // 2. Receber a prop userRole
586
+ userRole,
587
+ viewMembers = [],
588
+ onActivateQuestLens = () => {
589
+ }
588
590
  }) {
589
591
  const [query, setQuery] = useState2("");
590
592
  const [showList, setShowList] = useState2(false);
@@ -594,12 +596,16 @@ function XViewSidebar({
594
596
  const [isFilterMenuOpen, setIsFilterMenuOpen] = useState2(false);
595
597
  const [filterCategory, setFilterCategory] = useState2("Type");
596
598
  const [filterValue, setFilterValue] = useState2("");
599
+ const [questFilterAssignee, setQuestFilterAssignee] = useState2("");
600
+ const [questFilterStatus, setQuestFilterStatus] = useState2("");
601
+ const [isQuestLensMenuOpen, setIsQuestLensMenuOpen] = useState2(false);
597
602
  const [isTypeDropdownOpen, setIsTypeDropdownOpen] = useState2(false);
598
603
  const [highlightedTypeIndex, setHighlightedTypeIndex] = useState2(-1);
599
604
  const containerRef = useRef2(null);
600
605
  const inputRef = useRef2(null);
601
606
  const filterMenuRef = useRef2(null);
602
607
  const typeDropdownRef = useRef2(null);
608
+ const questLensRef = useRef2(null);
603
609
  const ability = useMemo2(() => {
604
610
  return defineAbilityFor(userRole);
605
611
  }, [userRole]);
@@ -704,6 +710,17 @@ function XViewSidebar({
704
710
  }
705
711
  return () => document.removeEventListener("mousedown", handleClickOutside);
706
712
  }, [isFilterMenuOpen, isTypeDropdownOpen]);
713
+ useEffect2(() => {
714
+ const handleClickOutsideQuest = (event) => {
715
+ if (questLensRef.current && !questLensRef.current.contains(event.target)) {
716
+ setIsQuestLensMenuOpen(false);
717
+ }
718
+ };
719
+ if (isQuestLensMenuOpen) {
720
+ document.addEventListener("mousedown", handleClickOutsideQuest);
721
+ }
722
+ return () => document.removeEventListener("mousedown", handleClickOutsideQuest);
723
+ }, [isQuestLensMenuOpen]);
707
724
  useEffect2(() => {
708
725
  if (!query) setSelectedNodeId(null);
709
726
  }, [query]);
@@ -995,7 +1012,58 @@ function XViewSidebar({
995
1012
  },
996
1013
  /* @__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("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }), /* @__PURE__ */ React2.createElement("polyline", { points: "2 17 12 22 22 17" }), /* @__PURE__ */ React2.createElement("polyline", { points: "2 12 12 17 22 12" })),
997
1014
  "Ancestry Board"
998
- ))), /* @__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(
1015
+ ), /* @__PURE__ */ React2.createElement("div", { className: "relative", ref: questLensRef }, /* @__PURE__ */ React2.createElement(
1016
+ "button",
1017
+ {
1018
+ onClick: () => setIsQuestLensMenuOpen(!isQuestLensMenuOpen),
1019
+ className: `${buttonBaseClass} ${isQuestLensMenuOpen || questFilterAssignee || questFilterStatus ? "bg-indigo-500/30 border-indigo-400" : ""}`,
1020
+ title: "Abrir filtros da Lente de Quests"
1021
+ },
1022
+ /* @__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("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }), /* @__PURE__ */ React2.createElement("path", { d: "M9 12l2 2 4-4" })),
1023
+ "Quest Lens",
1024
+ (questFilterAssignee || questFilterStatus) && /* @__PURE__ */ React2.createElement("span", { className: "ml-1 px-1.5 py-0.5 rounded-full bg-indigo-500 text-[9px] font-bold" }, "!")
1025
+ ), isQuestLensMenuOpen && /* @__PURE__ */ React2.createElement("div", { className: "absolute left-0 bottom-full mb-2 w-full p-4 rounded-xl bg-slate-900 border border-white/10 shadow-2xl z-50 flex flex-col gap-4 animate-in fade-in slide-in-from-bottom-2 duration-200" }, /* @__PURE__ */ React2.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React2.createElement("label", { className: "text-[10px] uppercase text-slate-400 font-bold tracking-widest" }, "Respons\xE1vel"), /* @__PURE__ */ React2.createElement(
1026
+ "select",
1027
+ {
1028
+ value: questFilterAssignee,
1029
+ onChange: (e) => setQuestFilterAssignee(e.target.value),
1030
+ className: "w-full bg-slate-800 p-2 text-xs rounded border border-white/10 focus:outline-none focus:border-indigo-500/50 text-white appearance-none cursor-pointer"
1031
+ },
1032
+ /* @__PURE__ */ React2.createElement("option", { value: "" }, "Todos os Respons\xE1veis"),
1033
+ viewMembers.map((m) => /* @__PURE__ */ React2.createElement("option", { key: m.id, value: m.id }, m.name || m.email))
1034
+ )), /* @__PURE__ */ React2.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React2.createElement("label", { className: "text-[10px] uppercase text-slate-400 font-bold tracking-widest" }, "Status"), /* @__PURE__ */ React2.createElement(
1035
+ "select",
1036
+ {
1037
+ value: questFilterStatus,
1038
+ onChange: (e) => setQuestFilterStatus(e.target.value),
1039
+ className: "w-full bg-slate-800 p-2 text-xs rounded border border-white/10 focus:outline-none focus:border-indigo-500/50 text-white appearance-none cursor-pointer"
1040
+ },
1041
+ /* @__PURE__ */ React2.createElement("option", { value: "" }, "Todos (Exclui Done)"),
1042
+ /* @__PURE__ */ React2.createElement("option", { value: "Backlog" }, "Backlog"),
1043
+ /* @__PURE__ */ React2.createElement("option", { value: "In Progress" }, "In Progress"),
1044
+ /* @__PURE__ */ React2.createElement("option", { value: "Review" }, "Review"),
1045
+ /* @__PURE__ */ React2.createElement("option", { value: "Done" }, "Done")
1046
+ )), /* @__PURE__ */ React2.createElement("div", { className: "flex gap-2 pt-1" }, /* @__PURE__ */ React2.createElement(
1047
+ "button",
1048
+ {
1049
+ onClick: () => {
1050
+ setQuestFilterAssignee("");
1051
+ setQuestFilterStatus("");
1052
+ },
1053
+ className: "flex-1 py-2 bg-white/5 hover:bg-white/10 text-slate-300 text-[11px] font-medium rounded-lg transition-colors border border-white/5"
1054
+ },
1055
+ "Limpar"
1056
+ ), /* @__PURE__ */ React2.createElement(
1057
+ "button",
1058
+ {
1059
+ onClick: () => {
1060
+ onActivateQuestLens({ assigneeId: questFilterAssignee, status: questFilterStatus });
1061
+ setIsQuestLensMenuOpen(false);
1062
+ },
1063
+ 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"
1064
+ },
1065
+ "Aplicar Lente"
1066
+ )))))), /* @__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(
999
1067
  "button",
1000
1068
  {
1001
1069
  onClick: handleSaveClick,
@@ -9949,7 +10017,8 @@ function QuestDetailsPanel({
9949
10017
  const newEvent = createTimelineEntry(
9950
10018
  actorId,
9951
10019
  `Status "In Progress" - Respons\xE1vel: ${targetName}`,
9952
- "status_changed"
10020
+ "status_changed",
10021
+ { metadata: { to_assignee_id: returnAssigneeId } }
9953
10022
  );
9954
10023
  const updatedTimeline = [...timeline, newEvent];
9955
10024
  const updatedStatus = "In Progress";
@@ -10069,7 +10138,8 @@ ${noteText}` : noteText : entry.notes,
10069
10138
  const newEvent = createTimelineEntry(
10070
10139
  actorId,
10071
10140
  `Status "${newStatus}" - Respons\xE1vel: ${assigneeName}`,
10072
- "status_changed"
10141
+ "status_changed",
10142
+ { metadata: { to_assignee_id: assigneeId } }
10073
10143
  );
10074
10144
  const newTimeline = [...timeline, newEvent];
10075
10145
  setTimeline(newTimeline);
@@ -11987,6 +12057,9 @@ var TIMELINE_NODE_SPACING_X = 20;
11987
12057
  var TIMELINE_INTERVAL_BAR_RADIUS = 0.2;
11988
12058
  var TIMELINE_INTERVAL_BAR_OPACITY = 0.85;
11989
12059
  var TIMELINE_GOLD_COLOR = 16766720;
12060
+ var QUEST_LENS_COLUMN_SPACING = 80;
12061
+ var QUEST_LENS_ROW_SPACING = 40;
12062
+ var QUEST_STATUS_ORDER = ["Backlog", "In Progress", "Review", "Done"];
11990
12063
  var getAllNodeIdsFromTree = (treeNode) => {
11991
12064
  const ids = /* @__PURE__ */ new Set();
11992
12065
  function traverse(node) {
@@ -12686,6 +12759,82 @@ function XViewScene({
12686
12759
  }
12687
12760
  });
12688
12761
  }, []);
12762
+ const handleActivateQuestLens = useCallback4(({ assigneeId, status: status2 }) => {
12763
+ var _a2;
12764
+ const { nodeObjects, tweenGroup, graphGroup } = stateRef.current;
12765
+ if (!nodeObjects || !tweenGroup || !graphGroup) return;
12766
+ const allQuests = ((_a2 = sceneDataRef.current) == null ? void 0 : _a2.quest_nodes) || [];
12767
+ const filteredQuests = allQuests.filter((q) => {
12768
+ const matchAssignee = !assigneeId || String(q.assignee_id) === String(assigneeId);
12769
+ const matchStatus = !status2 ? q.status !== "Done" : String(q.status) === String(status2);
12770
+ return matchAssignee && matchStatus;
12771
+ });
12772
+ if (filteredQuests.length === 0) {
12773
+ console.warn("Nenhuma quest encontrada para os filtros selecionados.");
12774
+ return;
12775
+ }
12776
+ const questsByStatus = {
12777
+ Backlog: [],
12778
+ "In Progress": [],
12779
+ Review: [],
12780
+ Done: []
12781
+ };
12782
+ filteredQuests.forEach((q) => {
12783
+ if (questsByStatus[q.status]) {
12784
+ questsByStatus[q.status].push(q);
12785
+ }
12786
+ });
12787
+ const startX = -((QUEST_STATUS_ORDER.length - 1) * QUEST_LENS_COLUMN_SPACING) / 2;
12788
+ Object.keys(nodeObjects).forEach((id) => {
12789
+ const mesh = nodeObjects[id];
12790
+ const isFilteredQuest = filteredQuests.some(
12791
+ (q) => String(q.id) === String(id)
12792
+ );
12793
+ if (!isFilteredQuest) {
12794
+ if (mesh.material) {
12795
+ const fadeTween = new Tween2(mesh.material).to({ opacity: 0.15 }, 1200).easing(Easing2.Cubic.Out);
12796
+ tweenGroup.add(fadeTween);
12797
+ fadeTween.start();
12798
+ }
12799
+ if (mesh.userData.labelObject) {
12800
+ mesh.userData.labelObject.visible = false;
12801
+ }
12802
+ } else {
12803
+ if (mesh.material) {
12804
+ const fadeInTween = new Tween2(mesh.material).to({ opacity: 1 }, 1200).easing(Easing2.Cubic.Out);
12805
+ tweenGroup.add(fadeInTween);
12806
+ fadeInTween.start();
12807
+ }
12808
+ if (mesh.userData.labelObject) {
12809
+ mesh.userData.labelObject.visible = true;
12810
+ }
12811
+ }
12812
+ });
12813
+ QUEST_STATUS_ORDER.forEach((statusName, colIndex) => {
12814
+ if (status2 && status2 !== statusName) return;
12815
+ const questsInCol = questsByStatus[statusName];
12816
+ const colX = startX + colIndex * QUEST_LENS_COLUMN_SPACING;
12817
+ questsInCol.forEach((q, rowIndex) => {
12818
+ const nodeId = String(q.id);
12819
+ let mesh = nodeObjects[nodeId];
12820
+ if (!mesh) {
12821
+ mesh = addOrUpdateNodeMesh(q, new THREE3.Vector3(colX, 150, 0), true);
12822
+ }
12823
+ const targetY = (questsInCol.length - 1) * 0.5 * QUEST_LENS_ROW_SPACING - rowIndex * QUEST_LENS_ROW_SPACING;
12824
+ const lensTween = new Tween2(mesh.position).to({ x: colX, y: targetY, z: 0 }, 1500).easing(Easing2.Cubic.InOut);
12825
+ tweenGroup.add(lensTween);
12826
+ lensTween.start();
12827
+ if (mesh.userData.labelObject) {
12828
+ const labelTargetPos = new THREE3.Vector3(colX, targetY, 0).add(
12829
+ mesh.userData.labelOffset || new THREE3.Vector3(0, -2.5, 0)
12830
+ );
12831
+ const labelTween = new Tween2(mesh.userData.labelObject.position).to(labelTargetPos, 1500).easing(Easing2.Cubic.InOut);
12832
+ tweenGroup.add(labelTween);
12833
+ labelTween.start();
12834
+ }
12835
+ });
12836
+ });
12837
+ }, [addOrUpdateNodeMesh]);
12689
12838
  const handleVersionTimeline = useCallback4((sourceMesh, versionMeshes) => {
12690
12839
  const { tweenGroup, timelineIntervalsGroup } = stateRef.current;
12691
12840
  if (!tweenGroup || !timelineIntervalsGroup || versionMeshes.length === 0)
@@ -14065,7 +14214,7 @@ function XViewScene({
14065
14214
  setDetailsNode(updatedNode);
14066
14215
  }, [session, actionHandlerContext]);
14067
14216
  const handleFinishQuestQuick = useCallback4((questNode) => {
14068
- var _a2, _b2, _c2, _d2, _e2, _f2;
14217
+ var _a2, _b2, _c2, _d2;
14069
14218
  if (!questNode || !actionHandlerContext) return;
14070
14219
  const {
14071
14220
  labelObject,
@@ -14085,18 +14234,8 @@ function XViewScene({
14085
14234
  if (currentReviewerIdx !== -1 && reviewStack[currentReviewerIdx].userId === cleanQuestNode.assignee_id) {
14086
14235
  reviewStack[currentReviewerIdx].status = "completed";
14087
14236
  nextIdxToAssign = currentReviewerIdx + 1;
14088
- newTimeline.push(createTimelineEntry(
14089
- ((_a2 = session == null ? void 0 : session.user) == null ? void 0 : _a2.id) || cleanQuestNode.assignee_id,
14090
- `Status "Review" - Conclu\xEDda por ${((_b2 = viewMembers.find((m) => m.id === cleanQuestNode.assignee_id)) == null ? void 0 : _b2.name) || "revisor"}`,
14091
- "status_changed"
14092
- ));
14093
14237
  } else {
14094
14238
  nextIdxToAssign = currentReviewerIdx !== -1 ? currentReviewerIdx : reviewStack.length;
14095
- newTimeline.push(createTimelineEntry(
14096
- ((_c2 = session == null ? void 0 : session.user) == null ? void 0 : _c2.id) || cleanQuestNode.assignee_id,
14097
- `Status "Done" - Finalizado por ${((_d2 = viewMembers.find((m) => m.id === cleanQuestNode.assignee_id)) == null ? void 0 : _d2.name) || ((_e2 = session == null ? void 0 : session.user) == null ? void 0 : _e2.name) || "respons\xE1vel"}`,
14098
- "status_changed"
14099
- ));
14100
14239
  }
14101
14240
  if (nextIdxToAssign < reviewStack.length) {
14102
14241
  const remainingMandatory = reviewStack.slice(nextIdxToAssign).some((r) => r.mandatory);
@@ -14105,12 +14244,6 @@ function XViewScene({
14105
14244
  const nextReviewer = reviewStack[nextIdxToAssign];
14106
14245
  nextAssigneeId = nextReviewer.userId;
14107
14246
  nextStatus = "Review";
14108
- newTimeline.push(createTimelineEntry(
14109
- "system",
14110
- `Status "Review" - Respons\xE1vel: ${((_f2 = viewMembers.find((m) => m.id === nextAssigneeId)) == null ? void 0 : _f2.name) || "revisor"}`,
14111
- "status_changed",
14112
- { metadata: { to_assignee_id: nextAssigneeId } }
14113
- ));
14114
14247
  } else {
14115
14248
  nextStatus = "Done";
14116
14249
  for (let i = nextIdxToAssign; i < reviewStack.length; i++) {
@@ -14120,10 +14253,22 @@ function XViewScene({
14120
14253
  } else {
14121
14254
  nextStatus = "Done";
14122
14255
  }
14123
- if (nextStatus === "Done") {
14256
+ if (nextStatus === "Review") {
14257
+ const nextReviewerName = ((_a2 = viewMembers.find((m) => m.id === nextAssigneeId)) == null ? void 0 : _a2.name) || "revisor";
14124
14258
  newTimeline.push(createTimelineEntry(
14125
14259
  "system",
14126
- `Status "Done" - Quest Finalizada`,
14260
+ `Status "Review" - Respons\xE1vel: ${nextReviewerName}`,
14261
+ "status_changed",
14262
+ { metadata: { to_assignee_id: nextAssigneeId } }
14263
+ ));
14264
+ } else if (nextStatus === "Done") {
14265
+ const finisherName = ((_b2 = viewMembers.find((m) => {
14266
+ var _a3;
14267
+ return m.id === (((_a3 = session == null ? void 0 : session.user) == null ? void 0 : _a3.id) || cleanQuestNode.assignee_id);
14268
+ })) == null ? void 0 : _b2.name) || ((_c2 = session == null ? void 0 : session.user) == null ? void 0 : _c2.name) || "respons\xE1vel";
14269
+ newTimeline.push(createTimelineEntry(
14270
+ ((_d2 = session == null ? void 0 : session.user) == null ? void 0 : _d2.id) || cleanQuestNode.assignee_id,
14271
+ `Status "Done" - Finalizado por ${finisherName}`,
14127
14272
  "status_changed"
14128
14273
  ));
14129
14274
  }
@@ -16210,13 +16355,15 @@ function XViewScene({
16210
16355
  onOpenImportModal: () => setIsImportModalOpen(true),
16211
16356
  importSuccessMessage,
16212
16357
  onActivateTimeline: handleActivateTimeline,
16358
+ onActivateQuestLens: handleActivateQuestLens,
16213
16359
  onOpenAncestryBoard: handleOpenAncestryBoard,
16214
16360
  viewType: viewParams == null ? void 0 : viewParams.type,
16215
16361
  viewName: viewParams == null ? void 0 : viewParams.name,
16216
16362
  onNavigateBack: handleNavigateBack,
16217
16363
  isOpen: isSidebarOpen,
16218
16364
  onToggle: setIsSidebarOpen,
16219
- userRole: userPermissionRole
16365
+ userRole: userPermissionRole,
16366
+ viewMembers
16220
16367
  }
16221
16368
  ),
16222
16369
  creationMode.isActive && /* @__PURE__ */ React27.createElement(
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.34",
3
+ "version": "1.2.5-dev.36",
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",