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

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 +409 -87
  2. package/dist/index.mjs +413 -89
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2851,7 +2851,7 @@ var userActionHandlers = {
2851
2851
  }
2852
2852
  },
2853
2853
  handleSaveNodeDetails: async (context, updatedNode, keepOpen = false) => {
2854
- var _a;
2854
+ var _a, _b, _c;
2855
2855
  const { graphDataRef, sceneDataRef, stateRef, setters } = context;
2856
2856
  if (!graphDataRef.current || !sceneDataRef.current) return;
2857
2857
  const {
@@ -2872,15 +2872,66 @@ var userActionHandlers = {
2872
2872
  const { parentFileId, ownerId } = parentInfo;
2873
2873
  const specificParentData = JSON.parse(JSON.stringify(graphDataRef.current[parentFileId]));
2874
2874
  const nodeIndex = specificParentData.nodes.findIndex((n) => String(n.id) === String(nodeToSave.id));
2875
- if (nodeIndex > -1) {
2876
- specificParentData.nodes[nodeIndex] = { ...nodeToSave };
2877
- } else {
2875
+ if (nodeIndex === -1) {
2878
2876
  console.error("Node n\xE3o encontrado nos dados do parent para atualiza\xE7\xE3o:", nodeToSave.id);
2879
2877
  alert("Erro interno: Node n\xE3o encontrado para salvar.");
2880
2878
  return;
2881
2879
  }
2880
+ const oldNode = specificParentData.nodes[nodeIndex];
2881
+ const isNewDone = oldNode.status !== "Done" && nodeToSave.status === "Done";
2882
+ specificParentData.nodes[nodeIndex] = { ...nodeToSave };
2883
+ if (isNewDone && nodeToSave.is_quest && ((_a = nodeToSave.recurrence_config) == null ? void 0 : _a.enabled)) {
2884
+ const nextDateObj = calculateNextRecurrenceDate(nodeToSave.recurrence_config);
2885
+ if (nextDateObj) {
2886
+ const currentCounter = sceneDataRef.current.quest_counter || 1;
2887
+ const nextCounter = currentCounter + 1;
2888
+ sceneDataRef.current.quest_counter = nextCounter;
2889
+ let nextName = nodeToSave.raw_title || nodeToSave.name;
2890
+ const prefixParts = (nodeToSave.name || "").split(" - \xBB ");
2891
+ if (prefixParts.length > 1) {
2892
+ const prefixBase = prefixParts[0].split(" - ")[0];
2893
+ nextName = `${prefixBase} - ${nextCounter} - \xBB ${nodeToSave.raw_title || prefixParts[1]}`;
2894
+ }
2895
+ const nextRecurrenceConfig = { ...nodeToSave.recurrence_config };
2896
+ if (nextRecurrenceConfig.type === "custom") {
2897
+ nextRecurrenceConfig.custom_date = "";
2898
+ }
2899
+ const nextQuestNode = {
2900
+ ...JSON.parse(JSON.stringify(nodeToSave)),
2901
+ // Deep copy properties
2902
+ id: import_short_uuid.default.generate(),
2903
+ name: nextName,
2904
+ status: "Backlog",
2905
+ color: "#64748b",
2906
+ // QUEST_STATUS_COLORS["Backlog"]
2907
+ assignee_id: nodeToSave.recurrence_config.next_assignee_id || "",
2908
+ Date: nextDateObj,
2909
+ timeline: [{
2910
+ id: import_short_uuid.default.generate(),
2911
+ type: "status_changed",
2912
+ timestamp: Date.now(),
2913
+ actorId: "system",
2914
+ description: `Quest criada automaticamente via recorr\xEAncia a partir da quest #${nodeToSave.id}`,
2915
+ links: [],
2916
+ images: [],
2917
+ metadata: { status: "Backlog" }
2918
+ }],
2919
+ version_node: {
2920
+ is_version: true,
2921
+ parent_node: ((_b = nodeToSave.version_node) == null ? void 0 : _b.parent_node) || nodeToSave.id
2922
+ },
2923
+ recurrence_config: nextRecurrenceConfig
2924
+ };
2925
+ specificParentData.nodes.push(nextQuestNode);
2926
+ if (stateRef.current.nodeObjects[String(nodeToSave.id)]) {
2927
+ const pos = stateRef.current.nodeObjects[String(nodeToSave.id)].position.clone();
2928
+ pos.x += 12;
2929
+ addStandaloneNodeToScene(stateRef.current, nextQuestNode, pos);
2930
+ }
2931
+ }
2932
+ }
2882
2933
  try {
2883
- const isView = ((_a = context.viewType) == null ? void 0 : _a.toLowerCase()) === "view";
2934
+ const isView = ((_c = context.viewType) == null ? void 0 : _c.toLowerCase()) === "view";
2884
2935
  if (isView && parentFileId === context.sceneConfigId) {
2885
2936
  const visualNodeIndex = sceneDataRef.current.nodes.findIndex((n) => String(n.id) === String(nodeToSave.id));
2886
2937
  if (visualNodeIndex > -1) {
@@ -3736,10 +3787,44 @@ function createTimelineEntry(actorId, description, type = "comment", extras = {}
3736
3787
  id: img.id || v4_default(),
3737
3788
  name: img.name || "",
3738
3789
  value: img.value || ""
3739
- })),
3740
- metadata
3790
+ }))
3741
3791
  };
3742
3792
  }
3793
+ function calculateNextRecurrenceDate(config) {
3794
+ if (!config || !config.enabled) return null;
3795
+ const now = /* @__PURE__ */ new Date();
3796
+ let nextDate = new Date(now);
3797
+ switch (config.type) {
3798
+ case "daily":
3799
+ nextDate.setDate(now.getDate() + 1);
3800
+ break;
3801
+ case "weekly":
3802
+ const targetDay = parseInt(config.day_of_week, 10);
3803
+ const currentDay = now.getDay();
3804
+ let daysUntilNext = targetDay - currentDay;
3805
+ if (daysUntilNext <= 0) daysUntilNext += 7;
3806
+ nextDate.setDate(now.getDate() + daysUntilNext);
3807
+ break;
3808
+ case "custom":
3809
+ if (config.custom_date) {
3810
+ const parts = config.custom_date.split("/");
3811
+ if (parts.length === 3) {
3812
+ nextDate = new Date(parseInt(parts[2], 10), parseInt(parts[1], 10) - 1, parseInt(parts[0], 10));
3813
+ } else {
3814
+ return null;
3815
+ }
3816
+ } else {
3817
+ return null;
3818
+ }
3819
+ break;
3820
+ default:
3821
+ return null;
3822
+ }
3823
+ const d = String(nextDate.getDate()).padStart(2, "0");
3824
+ const m = String(nextDate.getMonth() + 1).padStart(2, "0");
3825
+ const y = nextDate.getFullYear();
3826
+ return { date: `${d}/${m}/${y}` };
3827
+ }
3743
3828
 
3744
3829
  // src/components/CustomPropertyDisplay.jsx
3745
3830
  var import_react4 = __toESM(require("react"));
@@ -8866,7 +8951,15 @@ function InSceneQuestForm({
8866
8951
  const [size, setSize] = (0, import_react17.useState)("medium");
8867
8952
  const [intensity, setIntensity] = (0, import_react17.useState)(0);
8868
8953
  const [description, setDescription] = (0, import_react17.useState)("");
8954
+ const [recurrenceConfig, setRecurrenceConfig] = (0, import_react17.useState)({
8955
+ enabled: false,
8956
+ type: "daily",
8957
+ day_of_week: 1,
8958
+ custom_date: "",
8959
+ next_assignee_id: ""
8960
+ });
8869
8961
  const [reviewStack, setReviewStack] = (0, import_react17.useState)([]);
8962
+ const [questDate, setQuestDate] = (0, import_react17.useState)(null);
8870
8963
  const [isStatusDropdownOpen, setIsStatusDropdownOpen] = (0, import_react17.useState)(false);
8871
8964
  const [isAssigneeDropdownOpen, setIsAssigneeDropdownOpen] = (0, import_react17.useState)(false);
8872
8965
  const [assigneeSearchQuery, setAssigneeSearchQuery] = (0, import_react17.useState)("");
@@ -8938,6 +9031,8 @@ function InSceneQuestForm({
8938
9031
  textureImageUrl: null,
8939
9032
  reviewStack,
8940
9033
  timeline: initialTimeline,
9034
+ Date: questDate,
9035
+ recurrence_config: recurrenceConfig,
8941
9036
  ...additionalData
8942
9037
  });
8943
9038
  };
@@ -9104,7 +9199,139 @@ function InSceneQuestForm({
9104
9199
  )), viewMembers.filter((member) => {
9105
9200
  const search = assigneeSearchQuery.toLowerCase();
9106
9201
  return (member.name || "").toLowerCase().includes(search) || (member.email || "").toLowerCase().includes(search);
9107
- }).length === 0 && assigneeSearchQuery && /* @__PURE__ */ import_react17.default.createElement("li", { className: "px-3 py-4 text-xs text-slate-500 text-center italic" }, "Nenhum membro encontrado")))))), /* @__PURE__ */ import_react17.default.createElement("div", { className: "pt-2" }, /* @__PURE__ */ import_react17.default.createElement(
9202
+ }).length === 0 && assigneeSearchQuery && /* @__PURE__ */ import_react17.default.createElement("li", { className: "px-3 py-4 text-xs text-slate-500 text-center italic" }, "Nenhum membro encontrado")))))), /* @__PURE__ */ import_react17.default.createElement("div", { className: "space-y-1.5 mt-2" }, /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ import_react17.default.createElement("label", { className: "text-xs text-slate-300 font-medium uppercase tracking-wider" }, "Prazo / Data"), !questDate && /* @__PURE__ */ import_react17.default.createElement(
9203
+ "button",
9204
+ {
9205
+ type: "button",
9206
+ onClick: () => setQuestDate({ type: "Data", value: "" }),
9207
+ className: "text-[10px] text-indigo-400 hover:text-indigo-300 font-bold uppercase transition-colors"
9208
+ },
9209
+ "+ Adicionar"
9210
+ )), questDate && /* @__PURE__ */ import_react17.default.createElement("div", { className: "p-3 bg-slate-900/60 rounded-xl border border-white/10 space-y-3 relative group" }, /* @__PURE__ */ import_react17.default.createElement(
9211
+ "button",
9212
+ {
9213
+ type: "button",
9214
+ onClick: () => setQuestDate(null),
9215
+ className: "absolute top-2 right-2 w-6 h-6 grid place-items-center rounded-full bg-red-500/10 text-red-400 opacity-0 group-hover:opacity-100 transition-opacity",
9216
+ title: "Remover data"
9217
+ },
9218
+ /* @__PURE__ */ import_react17.default.createElement(import_fi15.FiX, { size: 12 })
9219
+ ), /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-1 rounded-lg bg-slate-800/50 p-1" }, ["Data", "Ano", "Date Interval"].map((t) => /* @__PURE__ */ import_react17.default.createElement(
9220
+ "button",
9221
+ {
9222
+ key: t,
9223
+ type: "button",
9224
+ onClick: () => {
9225
+ const newDate = { type: t };
9226
+ if (t === "Date Interval") {
9227
+ newDate.start = "";
9228
+ newDate.end = "";
9229
+ } else {
9230
+ newDate.value = "";
9231
+ }
9232
+ setQuestDate(newDate);
9233
+ },
9234
+ className: `flex-1 text-[10px] px-2 py-1.5 rounded-md transition-colors ${questDate.type === t ? "bg-indigo-600 text-white font-semibold shadow" : "text-slate-400 hover:bg-slate-700/50"}`
9235
+ },
9236
+ t === "Date Interval" ? "Intervalo" : t
9237
+ ))), questDate.type === "Data" && /* @__PURE__ */ import_react17.default.createElement(
9238
+ "input",
9239
+ {
9240
+ type: "text",
9241
+ placeholder: "DD/MM/AAAA",
9242
+ value: questDate.value || "",
9243
+ onChange: (e) => {
9244
+ const rawValue = e.target.value;
9245
+ const digits = rawValue.replace(/\D/g, "");
9246
+ const len = digits.length;
9247
+ let formattedValue = digits;
9248
+ if (len > 2 && len <= 4)
9249
+ formattedValue = `${digits.slice(0, 2)}/${digits.slice(2)}`;
9250
+ else if (len > 4)
9251
+ formattedValue = `${digits.slice(0, 2)}/${digits.slice(2, 4)}/${digits.slice(4, 8)}`;
9252
+ setQuestDate({ ...questDate, value: formattedValue });
9253
+ },
9254
+ maxLength: "10",
9255
+ className: "w-full bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-1 focus:ring-indigo-400/60"
9256
+ }
9257
+ ), questDate.type === "Ano" && /* @__PURE__ */ import_react17.default.createElement(
9258
+ "input",
9259
+ {
9260
+ type: "number",
9261
+ placeholder: "AAAA",
9262
+ value: questDate.value || "",
9263
+ onChange: (e) => setQuestDate({ ...questDate, value: e.target.value }),
9264
+ className: "w-full bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-1 focus:ring-indigo-400/60 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
9265
+ }
9266
+ ), questDate.type === "Date Interval" && /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react17.default.createElement(
9267
+ "input",
9268
+ {
9269
+ type: "number",
9270
+ placeholder: "In\xEDcio (Ano)",
9271
+ value: questDate.start || "",
9272
+ onChange: (e) => setQuestDate({ ...questDate, start: e.target.value }),
9273
+ className: "flex-1 bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-1 focus:ring-indigo-400/60 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
9274
+ }
9275
+ ), /* @__PURE__ */ import_react17.default.createElement("span", { className: "text-slate-500" }, "-"), /* @__PURE__ */ import_react17.default.createElement(
9276
+ "input",
9277
+ {
9278
+ type: "number",
9279
+ placeholder: "Fim (Ano)",
9280
+ value: questDate.end || "",
9281
+ onChange: (e) => setQuestDate({ ...questDate, end: e.target.value }),
9282
+ className: "flex-1 bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-1 focus:ring-indigo-400/60 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
9283
+ }
9284
+ )))), /* @__PURE__ */ import_react17.default.createElement("div", { className: "pt-4 border-t border-white/5 mt-2" }, /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center justify-between mb-3" }, /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react17.default.createElement(import_fi15.FiRepeat, { className: `w-3.5 h-3.5 ${recurrenceConfig.enabled ? "text-indigo-400" : "text-slate-500"}` }), /* @__PURE__ */ import_react17.default.createElement("span", { className: "text-xs text-slate-300 font-medium uppercase tracking-wider" }, "Recorr\xEAncia")), /* @__PURE__ */ import_react17.default.createElement(
9285
+ "button",
9286
+ {
9287
+ type: "button",
9288
+ onClick: () => setRecurrenceConfig((prev) => ({ ...prev, enabled: !prev.enabled })),
9289
+ className: `relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none ${recurrenceConfig.enabled ? "bg-indigo-600" : "bg-slate-700"} cursor-pointer`
9290
+ },
9291
+ /* @__PURE__ */ import_react17.default.createElement("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform ${recurrenceConfig.enabled ? "translate-x-4.5" : "translate-x-1"}` })
9292
+ )), recurrenceConfig.enabled && /* @__PURE__ */ import_react17.default.createElement("div", { className: "space-y-4 p-3 rounded-xl bg-white/5 border border-white/5 animate-in fade-in slide-in-from-top-1" }, /* @__PURE__ */ import_react17.default.createElement("div", { className: "grid grid-cols-2 gap-3" }, /* @__PURE__ */ import_react17.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react17.default.createElement("label", { className: "text-[10px] text-slate-500 uppercase font-bold tracking-tight" }, "Tipo"), /* @__PURE__ */ import_react17.default.createElement(
9293
+ "select",
9294
+ {
9295
+ value: recurrenceConfig.type,
9296
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, type: e.target.value })),
9297
+ className: "w-full bg-slate-900/50 border border-white/10 rounded-lg px-2 py-1.5 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-indigo-500/50"
9298
+ },
9299
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: "daily" }, "Di\xE1ria"),
9300
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: "weekly" }, "Semanal"),
9301
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: "custom" }, "Data Espec\xEDfica")
9302
+ )), recurrenceConfig.type === "weekly" && /* @__PURE__ */ import_react17.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react17.default.createElement("label", { className: "text-[10px] text-slate-500 uppercase font-bold tracking-tight" }, "Dia da Semana"), /* @__PURE__ */ import_react17.default.createElement(
9303
+ "select",
9304
+ {
9305
+ value: recurrenceConfig.day_of_week,
9306
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, day_of_week: parseInt(e.target.value, 10) })),
9307
+ className: "w-full bg-slate-900/50 border border-white/10 rounded-lg px-2 py-1.5 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-indigo-500/50"
9308
+ },
9309
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 1 }, "Segunda-feira"),
9310
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 2 }, "Ter\xE7a-feira"),
9311
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 3 }, "Quarta-feira"),
9312
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 4 }, "Quinta-feira"),
9313
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 5 }, "Sexta-feira"),
9314
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 6 }, "S\xE1bado"),
9315
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 0 }, "Domingo")
9316
+ )), recurrenceConfig.type === "custom" && /* @__PURE__ */ import_react17.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react17.default.createElement("label", { className: "text-[10px] text-slate-500 uppercase font-bold tracking-tight" }, "Pr\xF3xima Data"), /* @__PURE__ */ import_react17.default.createElement(
9317
+ "input",
9318
+ {
9319
+ type: "text",
9320
+ placeholder: "DD/MM/AAAA",
9321
+ value: recurrenceConfig.custom_date,
9322
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, custom_date: e.target.value })),
9323
+ className: "w-full bg-slate-900/50 border border-white/10 rounded-lg px-2 py-1.5 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-indigo-500/50"
9324
+ }
9325
+ ))), /* @__PURE__ */ import_react17.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react17.default.createElement("label", { className: "text-[10px] text-slate-500 uppercase font-bold tracking-tight" }, "Pr\xF3ximo Respons\xE1vel"), /* @__PURE__ */ import_react17.default.createElement("div", { className: "relative" }, /* @__PURE__ */ import_react17.default.createElement(
9326
+ "select",
9327
+ {
9328
+ value: recurrenceConfig.next_assignee_id,
9329
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, next_assignee_id: e.target.value })),
9330
+ className: "w-full bg-slate-900/50 border border-white/10 rounded-lg pl-8 pr-2 py-1.5 text-xs text-slate-200 appearance-none focus:outline-none focus:ring-1 focus:ring-indigo-500/50"
9331
+ },
9332
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: "" }, "Sem respons\xE1vel"),
9333
+ viewMembers.map((member) => /* @__PURE__ */ import_react17.default.createElement("option", { key: member.id, value: member.id }, member.name || member.email))
9334
+ ), /* @__PURE__ */ import_react17.default.createElement(import_fi15.FiUser, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 w-3 h-3 text-slate-500" }), /* @__PURE__ */ import_react17.default.createElement(import_fi15.FiChevronDown, { className: "absolute right-2.5 top-1/2 -translate-y-1/2 w-3 h-3 text-slate-500 pointer-events-none" }))))), /* @__PURE__ */ import_react17.default.createElement("div", { className: "pt-2" }, /* @__PURE__ */ import_react17.default.createElement(
9108
9335
  QuestReviewStackEditor,
9109
9336
  {
9110
9337
  reviewStack,
@@ -9889,12 +10116,20 @@ function QuestDetailsPanel({
9889
10116
  const [isSaving, setIsSaving] = (0, import_react20.useState)(false);
9890
10117
  const [isLinkCopied, setIsLinkCopied] = (0, import_react20.useState)(false);
9891
10118
  const [hasUnsavedChanges, setHasUnsavedChanges] = (0, import_react20.useState)(false);
10119
+ const [questDate, setQuestDate] = (0, import_react20.useState)((node == null ? void 0 : node.Date) || null);
9892
10120
  const [noteText, setNoteText] = (0, import_react20.useState)("");
9893
10121
  const [noteLinks, setNoteLinks] = (0, import_react20.useState)([]);
9894
10122
  const [noteImages, setNoteImages] = (0, import_react20.useState)([]);
9895
10123
  const [isNoteFormOpen, setIsNoteFormOpen] = (0, import_react20.useState)(false);
9896
10124
  const [editingEntryId, setEditingEntryId] = (0, import_react20.useState)(null);
9897
10125
  const [editingEntryLabel, setEditingEntryLabel] = (0, import_react20.useState)("");
10126
+ const [recurrenceConfig, setRecurrenceConfig] = (0, import_react20.useState)((node == null ? void 0 : node.recurrence_config) || {
10127
+ enabled: false,
10128
+ type: "daily",
10129
+ day_of_week: 1,
10130
+ custom_date: "",
10131
+ next_assignee_id: ""
10132
+ });
9898
10133
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
9899
10134
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
9900
10135
  initialWidth: isReadMode ? 700 : 440,
@@ -9930,6 +10165,14 @@ function QuestDetailsPanel({
9930
10165
  setCustomProps(extractCustomPropsFromNode(node || {}));
9931
10166
  setReviewStack((node == null ? void 0 : node.reviewStack) || []);
9932
10167
  setTimeline((node == null ? void 0 : node.timeline) || []);
10168
+ setQuestDate((node == null ? void 0 : node.Date) || null);
10169
+ setRecurrenceConfig((node == null ? void 0 : node.recurrence_config) || {
10170
+ enabled: false,
10171
+ type: "daily",
10172
+ day_of_week: 1,
10173
+ custom_date: "",
10174
+ next_assignee_id: ""
10175
+ });
9933
10176
  setHasUnsavedChanges(false);
9934
10177
  }
9935
10178
  }, [node]);
@@ -10025,6 +10268,7 @@ function QuestDetailsPanel({
10025
10268
  setIsSaving(false);
10026
10269
  }
10027
10270
  };
10271
+ const filteredCustomProps = customProps.filter((p) => p.key !== "Date" && p.key !== "Data");
10028
10272
  const handleAddTimelineNote = async () => {
10029
10273
  if (!noteText.trim() && noteLinks.length === 0 && noteImages.length === 0 || isSaving) return;
10030
10274
  setIsSaving(true);
@@ -10181,6 +10425,7 @@ ${noteText}` : noteText : entry.notes,
10181
10425
  const currentTimeline = overrides.timeline !== void 0 ? overrides.timeline : timeline;
10182
10426
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
10183
10427
  const currentStatus = overrides.status !== void 0 ? overrides.status : status;
10428
+ const currentRecurrenceConfig = overrides.recurrenceConfig !== void 0 ? overrides.recurrenceConfig : recurrenceConfig;
10184
10429
  if (!keepOpen && !hasUnsavedChanges) {
10185
10430
  onClose();
10186
10431
  return;
@@ -10204,11 +10449,13 @@ ${noteText}` : noteText : entry.notes,
10204
10449
  status: currentStatus,
10205
10450
  assignee_id: currentAssigneeId || null,
10206
10451
  size,
10207
- description: currentDescription,
10452
+ description: currentDescription.trim(),
10208
10453
  description_sections: processedSections,
10454
+ intensity,
10455
+ recurrence_config: currentRecurrenceConfig,
10456
+ Date: questDate,
10209
10457
  useImageAsTexture: false,
10210
10458
  textureImageUrl: null,
10211
- intensity,
10212
10459
  is_quest: true,
10213
10460
  reviewStack: currentReviewStack,
10214
10461
  timeline: currentTimeline,
@@ -12570,7 +12817,7 @@ function XViewScene({
12570
12817
  }
12571
12818
  }
12572
12819
  Object.values(nodeObjects).forEach((mesh) => {
12573
- const oldLabel = mesh.getObjectByName("timelineLabel");
12820
+ const oldLabel = mesh.getObjectByName("timelineLabel") || mesh.getObjectByName("questDateLabel");
12574
12821
  if (oldLabel) {
12575
12822
  mesh.remove(oldLabel);
12576
12823
  if (oldLabel.material.map) oldLabel.material.map.dispose();
@@ -12717,82 +12964,6 @@ function XViewScene({
12717
12964
  }
12718
12965
  });
12719
12966
  }, []);
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]);
12796
12967
  const handleVersionTimeline = (0, import_react28.useCallback)((sourceMesh, versionMeshes) => {
12797
12968
  const { tweenGroup, timelineIntervalsGroup } = stateRef.current;
12798
12969
  if (!tweenGroup || !timelineIntervalsGroup || versionMeshes.length === 0)
@@ -13077,6 +13248,157 @@ function XViewScene({
13077
13248
  },
13078
13249
  []
13079
13250
  );
13251
+ const handleActivateQuestLens = (0, import_react28.useCallback)(({ assigneeId, status: status2 }) => {
13252
+ var _a2;
13253
+ const { nodeObjects, tweenGroup, graphGroup, timelineIntervalsGroup } = stateRef.current;
13254
+ if (!nodeObjects || !tweenGroup || !graphGroup) return;
13255
+ Object.values(nodeObjects).forEach((mesh) => {
13256
+ const oldLabel = mesh.getObjectByName("questDateLabel") || mesh.getObjectByName("timelineLabel");
13257
+ if (oldLabel) {
13258
+ mesh.remove(oldLabel);
13259
+ if (oldLabel.material.map) oldLabel.material.map.dispose();
13260
+ oldLabel.material.dispose();
13261
+ }
13262
+ });
13263
+ const allQuests = ((_a2 = sceneDataRef.current) == null ? void 0 : _a2.quest_nodes) || [];
13264
+ const filteredQuests = allQuests.filter((q) => {
13265
+ const matchAssignee = !assigneeId || String(q.assignee_id) === String(assigneeId);
13266
+ const matchStatus = !status2 ? q.status !== "Done" : String(q.status) === String(status2);
13267
+ return matchAssignee && matchStatus;
13268
+ });
13269
+ if (filteredQuests.length === 0) {
13270
+ console.warn("Nenhuma quest encontrada para os filtros selecionados.");
13271
+ return;
13272
+ }
13273
+ const questsByStatus = {
13274
+ Backlog: [],
13275
+ "In Progress": [],
13276
+ Review: [],
13277
+ Done: []
13278
+ };
13279
+ filteredQuests.forEach((q) => {
13280
+ if (questsByStatus[q.status]) {
13281
+ questsByStatus[q.status].push(q);
13282
+ }
13283
+ });
13284
+ const parseDate = (str) => {
13285
+ if (!str) return null;
13286
+ const parts = str.split("/");
13287
+ if (parts.length === 3) {
13288
+ const day = parseInt(parts[0], 10);
13289
+ const month = parseInt(parts[1], 10) - 1;
13290
+ const year = parseInt(parts[2], 10);
13291
+ if (!isNaN(day) && !isNaN(month) && !isNaN(year)) {
13292
+ return new Date(year, month, day);
13293
+ }
13294
+ }
13295
+ return null;
13296
+ };
13297
+ const timePoints = /* @__PURE__ */ new Set();
13298
+ const questTimeInfos = /* @__PURE__ */ new Map();
13299
+ filteredQuests.forEach((q) => {
13300
+ const dateProp = q.Date;
13301
+ let info = { isUndated: true, label: "" };
13302
+ if (dateProp) {
13303
+ if (dateProp.date) {
13304
+ const d = parseDate(dateProp.date);
13305
+ if (d) {
13306
+ info = { startDate: d, endDate: d, label: dateProp.date, isUndated: false };
13307
+ timePoints.add(d.getTime());
13308
+ }
13309
+ } else if (dateProp.year) {
13310
+ const y = parseInt(dateProp.year, 10);
13311
+ if (!isNaN(y)) {
13312
+ const d = new Date(y, 0, 1);
13313
+ info = { startDate: d, endDate: d, label: String(y), isUndated: false };
13314
+ timePoints.add(d.getTime());
13315
+ }
13316
+ } else if (dateProp.date_interval && dateProp.date_interval.start) {
13317
+ const sY = parseInt(dateProp.date_interval.start, 10);
13318
+ const eY = parseInt(dateProp.date_interval.end, 10) || sY;
13319
+ if (!isNaN(sY)) {
13320
+ const sd = new Date(sY, 0, 1);
13321
+ const ed = new Date(eY, 11, 31);
13322
+ info = { startDate: sd, endDate: ed, label: `${sY}-${eY}`, isUndated: false };
13323
+ timePoints.add(sd.getTime());
13324
+ timePoints.add(ed.getTime());
13325
+ }
13326
+ }
13327
+ }
13328
+ questTimeInfos.set(String(q.id), info);
13329
+ });
13330
+ const sortedTimePoints = Array.from(timePoints).sort((a, b) => a - b);
13331
+ const timeToYMap = /* @__PURE__ */ new Map();
13332
+ if (sortedTimePoints.length > 0) {
13333
+ const maxIdx = sortedTimePoints.length - 1;
13334
+ sortedTimePoints.forEach((t, i) => {
13335
+ timeToYMap.set(t, (i - maxIdx) * (QUEST_LENS_ROW_SPACING || 30));
13336
+ });
13337
+ }
13338
+ const startX = -((QUEST_STATUS_ORDER.length - 1) * QUEST_LENS_COLUMN_SPACING) / 2;
13339
+ Object.keys(nodeObjects).forEach((id) => {
13340
+ const mesh = nodeObjects[id];
13341
+ const isFilteredQuest = filteredQuests.some(
13342
+ (q) => String(q.id) === String(id)
13343
+ );
13344
+ if (!isFilteredQuest) {
13345
+ if (mesh.material) {
13346
+ const fadeTween = new import_tween2.Tween(mesh.material).to({ opacity: 0.15 }, 1200).easing(import_tween2.Easing.Cubic.Out);
13347
+ tweenGroup.add(fadeTween);
13348
+ fadeTween.start();
13349
+ }
13350
+ if (mesh.userData.labelObject) {
13351
+ mesh.userData.labelObject.visible = false;
13352
+ }
13353
+ } else {
13354
+ if (mesh.material) {
13355
+ const fadeInTween = new import_tween2.Tween(mesh.material).to({ opacity: 1 }, 1200).easing(import_tween2.Easing.Cubic.Out);
13356
+ tweenGroup.add(fadeInTween);
13357
+ fadeInTween.start();
13358
+ }
13359
+ if (mesh.userData.labelObject) {
13360
+ mesh.userData.labelObject.visible = true;
13361
+ }
13362
+ }
13363
+ });
13364
+ QUEST_STATUS_ORDER.forEach((statusName, colIndex) => {
13365
+ if (status2 && status2 !== statusName) return;
13366
+ const questsInCol = questsByStatus[statusName];
13367
+ const colX = startX + colIndex * QUEST_LENS_COLUMN_SPACING;
13368
+ questsInCol.forEach((q, rowIndex) => {
13369
+ const nodeId = String(q.id);
13370
+ let mesh = nodeObjects[nodeId];
13371
+ if (!mesh) {
13372
+ mesh = addOrUpdateNodeMesh(q, new THREE3.Vector3(colX, 150, 0), true);
13373
+ }
13374
+ const timeInfo = questTimeInfos.get(nodeId) || { isUndated: true };
13375
+ let targetY;
13376
+ if (!timeInfo.isUndated) {
13377
+ targetY = timeToYMap.get(timeInfo.startDate.getTime());
13378
+ } else {
13379
+ const undatedOffset = sortedTimePoints.length > 0 ? sortedTimePoints.length * 5 : 0;
13380
+ targetY = -80 - rowIndex * (QUEST_LENS_ROW_SPACING || 30) - undatedOffset;
13381
+ }
13382
+ const lensTween = new import_tween2.Tween(mesh.position).to({ x: colX, y: targetY, z: 0 }, 1500).easing(import_tween2.Easing.Cubic.InOut);
13383
+ tweenGroup.add(lensTween);
13384
+ lensTween.start();
13385
+ if (!timeInfo.isUndated && timeInfo.label) {
13386
+ const dateSprite = createTextSprite(timeInfo.label, 36);
13387
+ dateSprite.name = "questDateLabel";
13388
+ dateSprite.position.set(0, 3.5, 0);
13389
+ mesh.add(dateSprite);
13390
+ }
13391
+ if (mesh.userData.labelObject) {
13392
+ const labelTargetPos = new THREE3.Vector3(colX, targetY, 0).add(
13393
+ mesh.userData.labelOffset || new THREE3.Vector3(0, -2.5, 0)
13394
+ );
13395
+ const labelTween = new import_tween2.Tween(mesh.userData.labelObject.position).to(labelTargetPos, 1500).easing(import_tween2.Easing.Cubic.InOut);
13396
+ tweenGroup.add(labelTween);
13397
+ labelTween.start();
13398
+ }
13399
+ });
13400
+ });
13401
+ }, [addOrUpdateNodeMesh]);
13080
13402
  (0, import_react28.useEffect)(() => {
13081
13403
  if (!isInitialized || !sceneDataRef.current) return;
13082
13404
  const currentMount = mountRef.current;
package/dist/index.mjs CHANGED
@@ -2812,7 +2812,7 @@ var userActionHandlers = {
2812
2812
  }
2813
2813
  },
2814
2814
  handleSaveNodeDetails: async (context, updatedNode, keepOpen = false) => {
2815
- var _a;
2815
+ var _a, _b, _c;
2816
2816
  const { graphDataRef, sceneDataRef, stateRef, setters } = context;
2817
2817
  if (!graphDataRef.current || !sceneDataRef.current) return;
2818
2818
  const {
@@ -2833,15 +2833,66 @@ var userActionHandlers = {
2833
2833
  const { parentFileId, ownerId } = parentInfo;
2834
2834
  const specificParentData = JSON.parse(JSON.stringify(graphDataRef.current[parentFileId]));
2835
2835
  const nodeIndex = specificParentData.nodes.findIndex((n) => String(n.id) === String(nodeToSave.id));
2836
- if (nodeIndex > -1) {
2837
- specificParentData.nodes[nodeIndex] = { ...nodeToSave };
2838
- } else {
2836
+ if (nodeIndex === -1) {
2839
2837
  console.error("Node n\xE3o encontrado nos dados do parent para atualiza\xE7\xE3o:", nodeToSave.id);
2840
2838
  alert("Erro interno: Node n\xE3o encontrado para salvar.");
2841
2839
  return;
2842
2840
  }
2841
+ const oldNode = specificParentData.nodes[nodeIndex];
2842
+ const isNewDone = oldNode.status !== "Done" && nodeToSave.status === "Done";
2843
+ specificParentData.nodes[nodeIndex] = { ...nodeToSave };
2844
+ if (isNewDone && nodeToSave.is_quest && ((_a = nodeToSave.recurrence_config) == null ? void 0 : _a.enabled)) {
2845
+ const nextDateObj = calculateNextRecurrenceDate(nodeToSave.recurrence_config);
2846
+ if (nextDateObj) {
2847
+ const currentCounter = sceneDataRef.current.quest_counter || 1;
2848
+ const nextCounter = currentCounter + 1;
2849
+ sceneDataRef.current.quest_counter = nextCounter;
2850
+ let nextName = nodeToSave.raw_title || nodeToSave.name;
2851
+ const prefixParts = (nodeToSave.name || "").split(" - \xBB ");
2852
+ if (prefixParts.length > 1) {
2853
+ const prefixBase = prefixParts[0].split(" - ")[0];
2854
+ nextName = `${prefixBase} - ${nextCounter} - \xBB ${nodeToSave.raw_title || prefixParts[1]}`;
2855
+ }
2856
+ const nextRecurrenceConfig = { ...nodeToSave.recurrence_config };
2857
+ if (nextRecurrenceConfig.type === "custom") {
2858
+ nextRecurrenceConfig.custom_date = "";
2859
+ }
2860
+ const nextQuestNode = {
2861
+ ...JSON.parse(JSON.stringify(nodeToSave)),
2862
+ // Deep copy properties
2863
+ id: short.generate(),
2864
+ name: nextName,
2865
+ status: "Backlog",
2866
+ color: "#64748b",
2867
+ // QUEST_STATUS_COLORS["Backlog"]
2868
+ assignee_id: nodeToSave.recurrence_config.next_assignee_id || "",
2869
+ Date: nextDateObj,
2870
+ timeline: [{
2871
+ id: short.generate(),
2872
+ type: "status_changed",
2873
+ timestamp: Date.now(),
2874
+ actorId: "system",
2875
+ description: `Quest criada automaticamente via recorr\xEAncia a partir da quest #${nodeToSave.id}`,
2876
+ links: [],
2877
+ images: [],
2878
+ metadata: { status: "Backlog" }
2879
+ }],
2880
+ version_node: {
2881
+ is_version: true,
2882
+ parent_node: ((_b = nodeToSave.version_node) == null ? void 0 : _b.parent_node) || nodeToSave.id
2883
+ },
2884
+ recurrence_config: nextRecurrenceConfig
2885
+ };
2886
+ specificParentData.nodes.push(nextQuestNode);
2887
+ if (stateRef.current.nodeObjects[String(nodeToSave.id)]) {
2888
+ const pos = stateRef.current.nodeObjects[String(nodeToSave.id)].position.clone();
2889
+ pos.x += 12;
2890
+ addStandaloneNodeToScene(stateRef.current, nextQuestNode, pos);
2891
+ }
2892
+ }
2893
+ }
2843
2894
  try {
2844
- const isView = ((_a = context.viewType) == null ? void 0 : _a.toLowerCase()) === "view";
2895
+ const isView = ((_c = context.viewType) == null ? void 0 : _c.toLowerCase()) === "view";
2845
2896
  if (isView && parentFileId === context.sceneConfigId) {
2846
2897
  const visualNodeIndex = sceneDataRef.current.nodes.findIndex((n) => String(n.id) === String(nodeToSave.id));
2847
2898
  if (visualNodeIndex > -1) {
@@ -3697,10 +3748,44 @@ function createTimelineEntry(actorId, description, type = "comment", extras = {}
3697
3748
  id: img.id || v4_default(),
3698
3749
  name: img.name || "",
3699
3750
  value: img.value || ""
3700
- })),
3701
- metadata
3751
+ }))
3702
3752
  };
3703
3753
  }
3754
+ function calculateNextRecurrenceDate(config) {
3755
+ if (!config || !config.enabled) return null;
3756
+ const now = /* @__PURE__ */ new Date();
3757
+ let nextDate = new Date(now);
3758
+ switch (config.type) {
3759
+ case "daily":
3760
+ nextDate.setDate(now.getDate() + 1);
3761
+ break;
3762
+ case "weekly":
3763
+ const targetDay = parseInt(config.day_of_week, 10);
3764
+ const currentDay = now.getDay();
3765
+ let daysUntilNext = targetDay - currentDay;
3766
+ if (daysUntilNext <= 0) daysUntilNext += 7;
3767
+ nextDate.setDate(now.getDate() + daysUntilNext);
3768
+ break;
3769
+ case "custom":
3770
+ if (config.custom_date) {
3771
+ const parts = config.custom_date.split("/");
3772
+ if (parts.length === 3) {
3773
+ nextDate = new Date(parseInt(parts[2], 10), parseInt(parts[1], 10) - 1, parseInt(parts[0], 10));
3774
+ } else {
3775
+ return null;
3776
+ }
3777
+ } else {
3778
+ return null;
3779
+ }
3780
+ break;
3781
+ default:
3782
+ return null;
3783
+ }
3784
+ const d = String(nextDate.getDate()).padStart(2, "0");
3785
+ const m = String(nextDate.getMonth() + 1).padStart(2, "0");
3786
+ const y = nextDate.getFullYear();
3787
+ return { date: `${d}/${m}/${y}` };
3788
+ }
3704
3789
 
3705
3790
  // src/components/CustomPropertyDisplay.jsx
3706
3791
  import React3, { useState as useState4, useRef as useRef3, useEffect as useEffect3 } from "react";
@@ -8684,7 +8769,9 @@ import {
8684
8769
  FiX as FiX5,
8685
8770
  FiChevronDown as FiChevronDown6,
8686
8771
  FiUser as FiUser2,
8687
- FiSearch as FiSearch5
8772
+ FiSearch as FiSearch5,
8773
+ FiCalendar,
8774
+ FiRepeat
8688
8775
  } from "react-icons/fi";
8689
8776
 
8690
8777
  // src/components/QuestReviewStackEditor.jsx
@@ -8883,7 +8970,15 @@ function InSceneQuestForm({
8883
8970
  const [size, setSize] = useState17("medium");
8884
8971
  const [intensity, setIntensity] = useState17(0);
8885
8972
  const [description, setDescription] = useState17("");
8973
+ const [recurrenceConfig, setRecurrenceConfig] = useState17({
8974
+ enabled: false,
8975
+ type: "daily",
8976
+ day_of_week: 1,
8977
+ custom_date: "",
8978
+ next_assignee_id: ""
8979
+ });
8886
8980
  const [reviewStack, setReviewStack] = useState17([]);
8981
+ const [questDate, setQuestDate] = useState17(null);
8887
8982
  const [isStatusDropdownOpen, setIsStatusDropdownOpen] = useState17(false);
8888
8983
  const [isAssigneeDropdownOpen, setIsAssigneeDropdownOpen] = useState17(false);
8889
8984
  const [assigneeSearchQuery, setAssigneeSearchQuery] = useState17("");
@@ -8955,6 +9050,8 @@ function InSceneQuestForm({
8955
9050
  textureImageUrl: null,
8956
9051
  reviewStack,
8957
9052
  timeline: initialTimeline,
9053
+ Date: questDate,
9054
+ recurrence_config: recurrenceConfig,
8958
9055
  ...additionalData
8959
9056
  });
8960
9057
  };
@@ -9121,7 +9218,139 @@ function InSceneQuestForm({
9121
9218
  )), viewMembers.filter((member) => {
9122
9219
  const search = assigneeSearchQuery.toLowerCase();
9123
9220
  return (member.name || "").toLowerCase().includes(search) || (member.email || "").toLowerCase().includes(search);
9124
- }).length === 0 && assigneeSearchQuery && /* @__PURE__ */ React16.createElement("li", { className: "px-3 py-4 text-xs text-slate-500 text-center italic" }, "Nenhum membro encontrado")))))), /* @__PURE__ */ React16.createElement("div", { className: "pt-2" }, /* @__PURE__ */ React16.createElement(
9221
+ }).length === 0 && assigneeSearchQuery && /* @__PURE__ */ React16.createElement("li", { className: "px-3 py-4 text-xs text-slate-500 text-center italic" }, "Nenhum membro encontrado")))))), /* @__PURE__ */ React16.createElement("div", { className: "space-y-1.5 mt-2" }, /* @__PURE__ */ React16.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React16.createElement("label", { className: "text-xs text-slate-300 font-medium uppercase tracking-wider" }, "Prazo / Data"), !questDate && /* @__PURE__ */ React16.createElement(
9222
+ "button",
9223
+ {
9224
+ type: "button",
9225
+ onClick: () => setQuestDate({ type: "Data", value: "" }),
9226
+ className: "text-[10px] text-indigo-400 hover:text-indigo-300 font-bold uppercase transition-colors"
9227
+ },
9228
+ "+ Adicionar"
9229
+ )), questDate && /* @__PURE__ */ React16.createElement("div", { className: "p-3 bg-slate-900/60 rounded-xl border border-white/10 space-y-3 relative group" }, /* @__PURE__ */ React16.createElement(
9230
+ "button",
9231
+ {
9232
+ type: "button",
9233
+ onClick: () => setQuestDate(null),
9234
+ className: "absolute top-2 right-2 w-6 h-6 grid place-items-center rounded-full bg-red-500/10 text-red-400 opacity-0 group-hover:opacity-100 transition-opacity",
9235
+ title: "Remover data"
9236
+ },
9237
+ /* @__PURE__ */ React16.createElement(FiX5, { size: 12 })
9238
+ ), /* @__PURE__ */ React16.createElement("div", { className: "flex items-center gap-1 rounded-lg bg-slate-800/50 p-1" }, ["Data", "Ano", "Date Interval"].map((t) => /* @__PURE__ */ React16.createElement(
9239
+ "button",
9240
+ {
9241
+ key: t,
9242
+ type: "button",
9243
+ onClick: () => {
9244
+ const newDate = { type: t };
9245
+ if (t === "Date Interval") {
9246
+ newDate.start = "";
9247
+ newDate.end = "";
9248
+ } else {
9249
+ newDate.value = "";
9250
+ }
9251
+ setQuestDate(newDate);
9252
+ },
9253
+ className: `flex-1 text-[10px] px-2 py-1.5 rounded-md transition-colors ${questDate.type === t ? "bg-indigo-600 text-white font-semibold shadow" : "text-slate-400 hover:bg-slate-700/50"}`
9254
+ },
9255
+ t === "Date Interval" ? "Intervalo" : t
9256
+ ))), questDate.type === "Data" && /* @__PURE__ */ React16.createElement(
9257
+ "input",
9258
+ {
9259
+ type: "text",
9260
+ placeholder: "DD/MM/AAAA",
9261
+ value: questDate.value || "",
9262
+ onChange: (e) => {
9263
+ const rawValue = e.target.value;
9264
+ const digits = rawValue.replace(/\D/g, "");
9265
+ const len = digits.length;
9266
+ let formattedValue = digits;
9267
+ if (len > 2 && len <= 4)
9268
+ formattedValue = `${digits.slice(0, 2)}/${digits.slice(2)}`;
9269
+ else if (len > 4)
9270
+ formattedValue = `${digits.slice(0, 2)}/${digits.slice(2, 4)}/${digits.slice(4, 8)}`;
9271
+ setQuestDate({ ...questDate, value: formattedValue });
9272
+ },
9273
+ maxLength: "10",
9274
+ className: "w-full bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-1 focus:ring-indigo-400/60"
9275
+ }
9276
+ ), questDate.type === "Ano" && /* @__PURE__ */ React16.createElement(
9277
+ "input",
9278
+ {
9279
+ type: "number",
9280
+ placeholder: "AAAA",
9281
+ value: questDate.value || "",
9282
+ onChange: (e) => setQuestDate({ ...questDate, value: e.target.value }),
9283
+ className: "w-full bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-1 focus:ring-indigo-400/60 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
9284
+ }
9285
+ ), questDate.type === "Date Interval" && /* @__PURE__ */ React16.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React16.createElement(
9286
+ "input",
9287
+ {
9288
+ type: "number",
9289
+ placeholder: "In\xEDcio (Ano)",
9290
+ value: questDate.start || "",
9291
+ onChange: (e) => setQuestDate({ ...questDate, start: e.target.value }),
9292
+ className: "flex-1 bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-1 focus:ring-indigo-400/60 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
9293
+ }
9294
+ ), /* @__PURE__ */ React16.createElement("span", { className: "text-slate-500" }, "-"), /* @__PURE__ */ React16.createElement(
9295
+ "input",
9296
+ {
9297
+ type: "number",
9298
+ placeholder: "Fim (Ano)",
9299
+ value: questDate.end || "",
9300
+ onChange: (e) => setQuestDate({ ...questDate, end: e.target.value }),
9301
+ className: "flex-1 bg-slate-800/70 p-2 text-sm rounded-lg border border-white/10 focus:outline-none focus:ring-1 focus:ring-indigo-400/60 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
9302
+ }
9303
+ )))), /* @__PURE__ */ React16.createElement("div", { className: "pt-4 border-t border-white/5 mt-2" }, /* @__PURE__ */ React16.createElement("div", { className: "flex items-center justify-between mb-3" }, /* @__PURE__ */ React16.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React16.createElement(FiRepeat, { className: `w-3.5 h-3.5 ${recurrenceConfig.enabled ? "text-indigo-400" : "text-slate-500"}` }), /* @__PURE__ */ React16.createElement("span", { className: "text-xs text-slate-300 font-medium uppercase tracking-wider" }, "Recorr\xEAncia")), /* @__PURE__ */ React16.createElement(
9304
+ "button",
9305
+ {
9306
+ type: "button",
9307
+ onClick: () => setRecurrenceConfig((prev) => ({ ...prev, enabled: !prev.enabled })),
9308
+ className: `relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none ${recurrenceConfig.enabled ? "bg-indigo-600" : "bg-slate-700"} cursor-pointer`
9309
+ },
9310
+ /* @__PURE__ */ React16.createElement("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform ${recurrenceConfig.enabled ? "translate-x-4.5" : "translate-x-1"}` })
9311
+ )), recurrenceConfig.enabled && /* @__PURE__ */ React16.createElement("div", { className: "space-y-4 p-3 rounded-xl bg-white/5 border border-white/5 animate-in fade-in slide-in-from-top-1" }, /* @__PURE__ */ React16.createElement("div", { className: "grid grid-cols-2 gap-3" }, /* @__PURE__ */ React16.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React16.createElement("label", { className: "text-[10px] text-slate-500 uppercase font-bold tracking-tight" }, "Tipo"), /* @__PURE__ */ React16.createElement(
9312
+ "select",
9313
+ {
9314
+ value: recurrenceConfig.type,
9315
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, type: e.target.value })),
9316
+ className: "w-full bg-slate-900/50 border border-white/10 rounded-lg px-2 py-1.5 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-indigo-500/50"
9317
+ },
9318
+ /* @__PURE__ */ React16.createElement("option", { value: "daily" }, "Di\xE1ria"),
9319
+ /* @__PURE__ */ React16.createElement("option", { value: "weekly" }, "Semanal"),
9320
+ /* @__PURE__ */ React16.createElement("option", { value: "custom" }, "Data Espec\xEDfica")
9321
+ )), recurrenceConfig.type === "weekly" && /* @__PURE__ */ React16.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React16.createElement("label", { className: "text-[10px] text-slate-500 uppercase font-bold tracking-tight" }, "Dia da Semana"), /* @__PURE__ */ React16.createElement(
9322
+ "select",
9323
+ {
9324
+ value: recurrenceConfig.day_of_week,
9325
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, day_of_week: parseInt(e.target.value, 10) })),
9326
+ className: "w-full bg-slate-900/50 border border-white/10 rounded-lg px-2 py-1.5 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-indigo-500/50"
9327
+ },
9328
+ /* @__PURE__ */ React16.createElement("option", { value: 1 }, "Segunda-feira"),
9329
+ /* @__PURE__ */ React16.createElement("option", { value: 2 }, "Ter\xE7a-feira"),
9330
+ /* @__PURE__ */ React16.createElement("option", { value: 3 }, "Quarta-feira"),
9331
+ /* @__PURE__ */ React16.createElement("option", { value: 4 }, "Quinta-feira"),
9332
+ /* @__PURE__ */ React16.createElement("option", { value: 5 }, "Sexta-feira"),
9333
+ /* @__PURE__ */ React16.createElement("option", { value: 6 }, "S\xE1bado"),
9334
+ /* @__PURE__ */ React16.createElement("option", { value: 0 }, "Domingo")
9335
+ )), recurrenceConfig.type === "custom" && /* @__PURE__ */ React16.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React16.createElement("label", { className: "text-[10px] text-slate-500 uppercase font-bold tracking-tight" }, "Pr\xF3xima Data"), /* @__PURE__ */ React16.createElement(
9336
+ "input",
9337
+ {
9338
+ type: "text",
9339
+ placeholder: "DD/MM/AAAA",
9340
+ value: recurrenceConfig.custom_date,
9341
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, custom_date: e.target.value })),
9342
+ className: "w-full bg-slate-900/50 border border-white/10 rounded-lg px-2 py-1.5 text-xs text-slate-200 focus:outline-none focus:ring-1 focus:ring-indigo-500/50"
9343
+ }
9344
+ ))), /* @__PURE__ */ React16.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React16.createElement("label", { className: "text-[10px] text-slate-500 uppercase font-bold tracking-tight" }, "Pr\xF3ximo Respons\xE1vel"), /* @__PURE__ */ React16.createElement("div", { className: "relative" }, /* @__PURE__ */ React16.createElement(
9345
+ "select",
9346
+ {
9347
+ value: recurrenceConfig.next_assignee_id,
9348
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, next_assignee_id: e.target.value })),
9349
+ className: "w-full bg-slate-900/50 border border-white/10 rounded-lg pl-8 pr-2 py-1.5 text-xs text-slate-200 appearance-none focus:outline-none focus:ring-1 focus:ring-indigo-500/50"
9350
+ },
9351
+ /* @__PURE__ */ React16.createElement("option", { value: "" }, "Sem respons\xE1vel"),
9352
+ viewMembers.map((member) => /* @__PURE__ */ React16.createElement("option", { key: member.id, value: member.id }, member.name || member.email))
9353
+ ), /* @__PURE__ */ React16.createElement(FiUser2, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 w-3 h-3 text-slate-500" }), /* @__PURE__ */ React16.createElement(FiChevronDown6, { className: "absolute right-2.5 top-1/2 -translate-y-1/2 w-3 h-3 text-slate-500 pointer-events-none" }))))), /* @__PURE__ */ React16.createElement("div", { className: "pt-2" }, /* @__PURE__ */ React16.createElement(
9125
9354
  QuestReviewStackEditor,
9126
9355
  {
9127
9356
  reviewStack,
@@ -9776,7 +10005,7 @@ function NodeDetailsPanel({
9776
10005
 
9777
10006
  // src/components/QuestDetailsPanel.jsx
9778
10007
  import React19, { useState as useState19, useEffect as useEffect16, useRef as useRef14 } from "react";
9779
- import { FiPlus as FiPlus8, FiX as FiX7, FiCheck as FiCheck11, FiEdit2 as FiEdit28, FiLoader as FiLoader3, FiBookOpen as FiBookOpen4, FiLink as FiLink7, FiTarget as FiTarget3, FiChevronDown as FiChevronDown7, FiUser as FiUser3, FiSearch as FiSearch7 } from "react-icons/fi";
10008
+ import { FiPlus as FiPlus8, FiX as FiX7, FiCheck as FiCheck11, FiEdit2 as FiEdit28, FiLoader as FiLoader3, FiBookOpen as FiBookOpen4, FiLink as FiLink7, FiTarget as FiTarget3, FiChevronDown as FiChevronDown7, FiUser as FiUser3, FiSearch as FiSearch7, FiRepeat as FiRepeat2 } from "react-icons/fi";
9780
10009
 
9781
10010
  // src/components/QuestTimeline.jsx
9782
10011
  import React18 from "react";
@@ -9918,12 +10147,20 @@ function QuestDetailsPanel({
9918
10147
  const [isSaving, setIsSaving] = useState19(false);
9919
10148
  const [isLinkCopied, setIsLinkCopied] = useState19(false);
9920
10149
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState19(false);
10150
+ const [questDate, setQuestDate] = useState19((node == null ? void 0 : node.Date) || null);
9921
10151
  const [noteText, setNoteText] = useState19("");
9922
10152
  const [noteLinks, setNoteLinks] = useState19([]);
9923
10153
  const [noteImages, setNoteImages] = useState19([]);
9924
10154
  const [isNoteFormOpen, setIsNoteFormOpen] = useState19(false);
9925
10155
  const [editingEntryId, setEditingEntryId] = useState19(null);
9926
10156
  const [editingEntryLabel, setEditingEntryLabel] = useState19("");
10157
+ const [recurrenceConfig, setRecurrenceConfig] = useState19((node == null ? void 0 : node.recurrence_config) || {
10158
+ enabled: false,
10159
+ type: "daily",
10160
+ day_of_week: 1,
10161
+ custom_date: "",
10162
+ next_assignee_id: ""
10163
+ });
9927
10164
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
9928
10165
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
9929
10166
  initialWidth: isReadMode ? 700 : 440,
@@ -9959,6 +10196,14 @@ function QuestDetailsPanel({
9959
10196
  setCustomProps(extractCustomPropsFromNode(node || {}));
9960
10197
  setReviewStack((node == null ? void 0 : node.reviewStack) || []);
9961
10198
  setTimeline((node == null ? void 0 : node.timeline) || []);
10199
+ setQuestDate((node == null ? void 0 : node.Date) || null);
10200
+ setRecurrenceConfig((node == null ? void 0 : node.recurrence_config) || {
10201
+ enabled: false,
10202
+ type: "daily",
10203
+ day_of_week: 1,
10204
+ custom_date: "",
10205
+ next_assignee_id: ""
10206
+ });
9962
10207
  setHasUnsavedChanges(false);
9963
10208
  }
9964
10209
  }, [node]);
@@ -10054,6 +10299,7 @@ function QuestDetailsPanel({
10054
10299
  setIsSaving(false);
10055
10300
  }
10056
10301
  };
10302
+ const filteredCustomProps = customProps.filter((p) => p.key !== "Date" && p.key !== "Data");
10057
10303
  const handleAddTimelineNote = async () => {
10058
10304
  if (!noteText.trim() && noteLinks.length === 0 && noteImages.length === 0 || isSaving) return;
10059
10305
  setIsSaving(true);
@@ -10210,6 +10456,7 @@ ${noteText}` : noteText : entry.notes,
10210
10456
  const currentTimeline = overrides.timeline !== void 0 ? overrides.timeline : timeline;
10211
10457
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
10212
10458
  const currentStatus = overrides.status !== void 0 ? overrides.status : status;
10459
+ const currentRecurrenceConfig = overrides.recurrenceConfig !== void 0 ? overrides.recurrenceConfig : recurrenceConfig;
10213
10460
  if (!keepOpen && !hasUnsavedChanges) {
10214
10461
  onClose();
10215
10462
  return;
@@ -10233,11 +10480,13 @@ ${noteText}` : noteText : entry.notes,
10233
10480
  status: currentStatus,
10234
10481
  assignee_id: currentAssigneeId || null,
10235
10482
  size,
10236
- description: currentDescription,
10483
+ description: currentDescription.trim(),
10237
10484
  description_sections: processedSections,
10485
+ intensity,
10486
+ recurrence_config: currentRecurrenceConfig,
10487
+ Date: questDate,
10238
10488
  useImageAsTexture: false,
10239
10489
  textureImageUrl: null,
10240
- intensity,
10241
10490
  is_quest: true,
10242
10491
  reviewStack: currentReviewStack,
10243
10492
  timeline: currentTimeline,
@@ -12612,7 +12861,7 @@ function XViewScene({
12612
12861
  }
12613
12862
  }
12614
12863
  Object.values(nodeObjects).forEach((mesh) => {
12615
- const oldLabel = mesh.getObjectByName("timelineLabel");
12864
+ const oldLabel = mesh.getObjectByName("timelineLabel") || mesh.getObjectByName("questDateLabel");
12616
12865
  if (oldLabel) {
12617
12866
  mesh.remove(oldLabel);
12618
12867
  if (oldLabel.material.map) oldLabel.material.map.dispose();
@@ -12759,82 +13008,6 @@ function XViewScene({
12759
13008
  }
12760
13009
  });
12761
13010
  }, []);
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]);
12838
13011
  const handleVersionTimeline = useCallback4((sourceMesh, versionMeshes) => {
12839
13012
  const { tweenGroup, timelineIntervalsGroup } = stateRef.current;
12840
13013
  if (!tweenGroup || !timelineIntervalsGroup || versionMeshes.length === 0)
@@ -13119,6 +13292,157 @@ function XViewScene({
13119
13292
  },
13120
13293
  []
13121
13294
  );
13295
+ const handleActivateQuestLens = useCallback4(({ assigneeId, status: status2 }) => {
13296
+ var _a2;
13297
+ const { nodeObjects, tweenGroup, graphGroup, timelineIntervalsGroup } = stateRef.current;
13298
+ if (!nodeObjects || !tweenGroup || !graphGroup) return;
13299
+ Object.values(nodeObjects).forEach((mesh) => {
13300
+ const oldLabel = mesh.getObjectByName("questDateLabel") || mesh.getObjectByName("timelineLabel");
13301
+ if (oldLabel) {
13302
+ mesh.remove(oldLabel);
13303
+ if (oldLabel.material.map) oldLabel.material.map.dispose();
13304
+ oldLabel.material.dispose();
13305
+ }
13306
+ });
13307
+ const allQuests = ((_a2 = sceneDataRef.current) == null ? void 0 : _a2.quest_nodes) || [];
13308
+ const filteredQuests = allQuests.filter((q) => {
13309
+ const matchAssignee = !assigneeId || String(q.assignee_id) === String(assigneeId);
13310
+ const matchStatus = !status2 ? q.status !== "Done" : String(q.status) === String(status2);
13311
+ return matchAssignee && matchStatus;
13312
+ });
13313
+ if (filteredQuests.length === 0) {
13314
+ console.warn("Nenhuma quest encontrada para os filtros selecionados.");
13315
+ return;
13316
+ }
13317
+ const questsByStatus = {
13318
+ Backlog: [],
13319
+ "In Progress": [],
13320
+ Review: [],
13321
+ Done: []
13322
+ };
13323
+ filteredQuests.forEach((q) => {
13324
+ if (questsByStatus[q.status]) {
13325
+ questsByStatus[q.status].push(q);
13326
+ }
13327
+ });
13328
+ const parseDate = (str) => {
13329
+ if (!str) return null;
13330
+ const parts = str.split("/");
13331
+ if (parts.length === 3) {
13332
+ const day = parseInt(parts[0], 10);
13333
+ const month = parseInt(parts[1], 10) - 1;
13334
+ const year = parseInt(parts[2], 10);
13335
+ if (!isNaN(day) && !isNaN(month) && !isNaN(year)) {
13336
+ return new Date(year, month, day);
13337
+ }
13338
+ }
13339
+ return null;
13340
+ };
13341
+ const timePoints = /* @__PURE__ */ new Set();
13342
+ const questTimeInfos = /* @__PURE__ */ new Map();
13343
+ filteredQuests.forEach((q) => {
13344
+ const dateProp = q.Date;
13345
+ let info = { isUndated: true, label: "" };
13346
+ if (dateProp) {
13347
+ if (dateProp.date) {
13348
+ const d = parseDate(dateProp.date);
13349
+ if (d) {
13350
+ info = { startDate: d, endDate: d, label: dateProp.date, isUndated: false };
13351
+ timePoints.add(d.getTime());
13352
+ }
13353
+ } else if (dateProp.year) {
13354
+ const y = parseInt(dateProp.year, 10);
13355
+ if (!isNaN(y)) {
13356
+ const d = new Date(y, 0, 1);
13357
+ info = { startDate: d, endDate: d, label: String(y), isUndated: false };
13358
+ timePoints.add(d.getTime());
13359
+ }
13360
+ } else if (dateProp.date_interval && dateProp.date_interval.start) {
13361
+ const sY = parseInt(dateProp.date_interval.start, 10);
13362
+ const eY = parseInt(dateProp.date_interval.end, 10) || sY;
13363
+ if (!isNaN(sY)) {
13364
+ const sd = new Date(sY, 0, 1);
13365
+ const ed = new Date(eY, 11, 31);
13366
+ info = { startDate: sd, endDate: ed, label: `${sY}-${eY}`, isUndated: false };
13367
+ timePoints.add(sd.getTime());
13368
+ timePoints.add(ed.getTime());
13369
+ }
13370
+ }
13371
+ }
13372
+ questTimeInfos.set(String(q.id), info);
13373
+ });
13374
+ const sortedTimePoints = Array.from(timePoints).sort((a, b) => a - b);
13375
+ const timeToYMap = /* @__PURE__ */ new Map();
13376
+ if (sortedTimePoints.length > 0) {
13377
+ const maxIdx = sortedTimePoints.length - 1;
13378
+ sortedTimePoints.forEach((t, i) => {
13379
+ timeToYMap.set(t, (i - maxIdx) * (QUEST_LENS_ROW_SPACING || 30));
13380
+ });
13381
+ }
13382
+ const startX = -((QUEST_STATUS_ORDER.length - 1) * QUEST_LENS_COLUMN_SPACING) / 2;
13383
+ Object.keys(nodeObjects).forEach((id) => {
13384
+ const mesh = nodeObjects[id];
13385
+ const isFilteredQuest = filteredQuests.some(
13386
+ (q) => String(q.id) === String(id)
13387
+ );
13388
+ if (!isFilteredQuest) {
13389
+ if (mesh.material) {
13390
+ const fadeTween = new Tween2(mesh.material).to({ opacity: 0.15 }, 1200).easing(Easing2.Cubic.Out);
13391
+ tweenGroup.add(fadeTween);
13392
+ fadeTween.start();
13393
+ }
13394
+ if (mesh.userData.labelObject) {
13395
+ mesh.userData.labelObject.visible = false;
13396
+ }
13397
+ } else {
13398
+ if (mesh.material) {
13399
+ const fadeInTween = new Tween2(mesh.material).to({ opacity: 1 }, 1200).easing(Easing2.Cubic.Out);
13400
+ tweenGroup.add(fadeInTween);
13401
+ fadeInTween.start();
13402
+ }
13403
+ if (mesh.userData.labelObject) {
13404
+ mesh.userData.labelObject.visible = true;
13405
+ }
13406
+ }
13407
+ });
13408
+ QUEST_STATUS_ORDER.forEach((statusName, colIndex) => {
13409
+ if (status2 && status2 !== statusName) return;
13410
+ const questsInCol = questsByStatus[statusName];
13411
+ const colX = startX + colIndex * QUEST_LENS_COLUMN_SPACING;
13412
+ questsInCol.forEach((q, rowIndex) => {
13413
+ const nodeId = String(q.id);
13414
+ let mesh = nodeObjects[nodeId];
13415
+ if (!mesh) {
13416
+ mesh = addOrUpdateNodeMesh(q, new THREE3.Vector3(colX, 150, 0), true);
13417
+ }
13418
+ const timeInfo = questTimeInfos.get(nodeId) || { isUndated: true };
13419
+ let targetY;
13420
+ if (!timeInfo.isUndated) {
13421
+ targetY = timeToYMap.get(timeInfo.startDate.getTime());
13422
+ } else {
13423
+ const undatedOffset = sortedTimePoints.length > 0 ? sortedTimePoints.length * 5 : 0;
13424
+ targetY = -80 - rowIndex * (QUEST_LENS_ROW_SPACING || 30) - undatedOffset;
13425
+ }
13426
+ const lensTween = new Tween2(mesh.position).to({ x: colX, y: targetY, z: 0 }, 1500).easing(Easing2.Cubic.InOut);
13427
+ tweenGroup.add(lensTween);
13428
+ lensTween.start();
13429
+ if (!timeInfo.isUndated && timeInfo.label) {
13430
+ const dateSprite = createTextSprite(timeInfo.label, 36);
13431
+ dateSprite.name = "questDateLabel";
13432
+ dateSprite.position.set(0, 3.5, 0);
13433
+ mesh.add(dateSprite);
13434
+ }
13435
+ if (mesh.userData.labelObject) {
13436
+ const labelTargetPos = new THREE3.Vector3(colX, targetY, 0).add(
13437
+ mesh.userData.labelOffset || new THREE3.Vector3(0, -2.5, 0)
13438
+ );
13439
+ const labelTween = new Tween2(mesh.userData.labelObject.position).to(labelTargetPos, 1500).easing(Easing2.Cubic.InOut);
13440
+ tweenGroup.add(labelTween);
13441
+ labelTween.start();
13442
+ }
13443
+ });
13444
+ });
13445
+ }, [addOrUpdateNodeMesh]);
13122
13446
  useEffect22(() => {
13123
13447
  if (!isInitialized || !sceneDataRef.current) return;
13124
13448
  const currentMount = mountRef.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.36",
3
+ "version": "1.2.5-dev.38",
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",