@lv-x-software-house/x_view 1.2.5-dev.37 → 1.2.5-dev.39

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 +338 -13
  2. package/dist/index.mjs +342 -15
  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,67 @@ 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
+ reviewStack: (nodeToSave.reviewStack || []).map((r) => ({ ...r, status: "pending" })),
2924
+ recurrence_config: nextRecurrenceConfig
2925
+ };
2926
+ specificParentData.nodes.push(nextQuestNode);
2927
+ if (stateRef.current.nodeObjects[String(nodeToSave.id)]) {
2928
+ const pos = stateRef.current.nodeObjects[String(nodeToSave.id)].position.clone();
2929
+ pos.x += 12;
2930
+ addStandaloneNodeToScene(stateRef.current, nextQuestNode, pos);
2931
+ }
2932
+ }
2933
+ }
2882
2934
  try {
2883
- const isView = ((_a = context.viewType) == null ? void 0 : _a.toLowerCase()) === "view";
2935
+ const isView = ((_c = context.viewType) == null ? void 0 : _c.toLowerCase()) === "view";
2884
2936
  if (isView && parentFileId === context.sceneConfigId) {
2885
2937
  const visualNodeIndex = sceneDataRef.current.nodes.findIndex((n) => String(n.id) === String(nodeToSave.id));
2886
2938
  if (visualNodeIndex > -1) {
@@ -3274,6 +3326,8 @@ var IGNORED_KEYS = [
3274
3326
  "is_quest",
3275
3327
  "raw_title",
3276
3328
  "assignee_id",
3329
+ "Date",
3330
+ "recurrence_config",
3277
3331
  "reviewStack",
3278
3332
  "timeline"
3279
3333
  ];
@@ -3736,10 +3790,44 @@ function createTimelineEntry(actorId, description, type = "comment", extras = {}
3736
3790
  id: img.id || v4_default(),
3737
3791
  name: img.name || "",
3738
3792
  value: img.value || ""
3739
- })),
3740
- metadata
3793
+ }))
3741
3794
  };
3742
3795
  }
3796
+ function calculateNextRecurrenceDate(config) {
3797
+ if (!config || !config.enabled) return null;
3798
+ const now = /* @__PURE__ */ new Date();
3799
+ let nextDate = new Date(now);
3800
+ switch (config.type) {
3801
+ case "daily":
3802
+ nextDate.setDate(now.getDate() + 1);
3803
+ break;
3804
+ case "weekly":
3805
+ const targetDay = parseInt(config.day_of_week, 10);
3806
+ const currentDay = now.getDay();
3807
+ let daysUntilNext = targetDay - currentDay;
3808
+ if (daysUntilNext <= 0) daysUntilNext += 7;
3809
+ nextDate.setDate(now.getDate() + daysUntilNext);
3810
+ break;
3811
+ case "custom":
3812
+ if (config.custom_date) {
3813
+ const parts = config.custom_date.split("/");
3814
+ if (parts.length === 3) {
3815
+ nextDate = new Date(parseInt(parts[2], 10), parseInt(parts[1], 10) - 1, parseInt(parts[0], 10));
3816
+ } else {
3817
+ return null;
3818
+ }
3819
+ } else {
3820
+ return null;
3821
+ }
3822
+ break;
3823
+ default:
3824
+ return null;
3825
+ }
3826
+ const d = String(nextDate.getDate()).padStart(2, "0");
3827
+ const m = String(nextDate.getMonth() + 1).padStart(2, "0");
3828
+ const y = nextDate.getFullYear();
3829
+ return { date: `${d}/${m}/${y}` };
3830
+ }
3743
3831
 
3744
3832
  // src/components/CustomPropertyDisplay.jsx
3745
3833
  var import_react4 = __toESM(require("react"));
@@ -8866,7 +8954,15 @@ function InSceneQuestForm({
8866
8954
  const [size, setSize] = (0, import_react17.useState)("medium");
8867
8955
  const [intensity, setIntensity] = (0, import_react17.useState)(0);
8868
8956
  const [description, setDescription] = (0, import_react17.useState)("");
8957
+ const [recurrenceConfig, setRecurrenceConfig] = (0, import_react17.useState)({
8958
+ enabled: false,
8959
+ type: "daily",
8960
+ day_of_week: 1,
8961
+ custom_date: "",
8962
+ next_assignee_id: ""
8963
+ });
8869
8964
  const [reviewStack, setReviewStack] = (0, import_react17.useState)([]);
8965
+ const [questDate, setQuestDate] = (0, import_react17.useState)(null);
8870
8966
  const [isStatusDropdownOpen, setIsStatusDropdownOpen] = (0, import_react17.useState)(false);
8871
8967
  const [isAssigneeDropdownOpen, setIsAssigneeDropdownOpen] = (0, import_react17.useState)(false);
8872
8968
  const [assigneeSearchQuery, setAssigneeSearchQuery] = (0, import_react17.useState)("");
@@ -8938,6 +9034,8 @@ function InSceneQuestForm({
8938
9034
  textureImageUrl: null,
8939
9035
  reviewStack,
8940
9036
  timeline: initialTimeline,
9037
+ Date: questDate,
9038
+ recurrence_config: recurrenceConfig,
8941
9039
  ...additionalData
8942
9040
  });
8943
9041
  };
@@ -9104,7 +9202,139 @@ function InSceneQuestForm({
9104
9202
  )), viewMembers.filter((member) => {
9105
9203
  const search = assigneeSearchQuery.toLowerCase();
9106
9204
  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(
9205
+ }).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(
9206
+ "button",
9207
+ {
9208
+ type: "button",
9209
+ onClick: () => setQuestDate({ type: "Data", value: "" }),
9210
+ className: "text-[10px] text-indigo-400 hover:text-indigo-300 font-bold uppercase transition-colors"
9211
+ },
9212
+ "+ Adicionar"
9213
+ )), 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(
9214
+ "button",
9215
+ {
9216
+ type: "button",
9217
+ onClick: () => setQuestDate(null),
9218
+ 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",
9219
+ title: "Remover data"
9220
+ },
9221
+ /* @__PURE__ */ import_react17.default.createElement(import_fi15.FiX, { size: 12 })
9222
+ ), /* @__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(
9223
+ "button",
9224
+ {
9225
+ key: t,
9226
+ type: "button",
9227
+ onClick: () => {
9228
+ const newDate = { type: t };
9229
+ if (t === "Date Interval") {
9230
+ newDate.start = "";
9231
+ newDate.end = "";
9232
+ } else {
9233
+ newDate.value = "";
9234
+ }
9235
+ setQuestDate(newDate);
9236
+ },
9237
+ 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"}`
9238
+ },
9239
+ t === "Date Interval" ? "Intervalo" : t
9240
+ ))), questDate.type === "Data" && /* @__PURE__ */ import_react17.default.createElement(
9241
+ "input",
9242
+ {
9243
+ type: "text",
9244
+ placeholder: "DD/MM/AAAA",
9245
+ value: questDate.value || "",
9246
+ onChange: (e) => {
9247
+ const rawValue = e.target.value;
9248
+ const digits = rawValue.replace(/\D/g, "");
9249
+ const len = digits.length;
9250
+ let formattedValue = digits;
9251
+ if (len > 2 && len <= 4)
9252
+ formattedValue = `${digits.slice(0, 2)}/${digits.slice(2)}`;
9253
+ else if (len > 4)
9254
+ formattedValue = `${digits.slice(0, 2)}/${digits.slice(2, 4)}/${digits.slice(4, 8)}`;
9255
+ setQuestDate({ ...questDate, value: formattedValue });
9256
+ },
9257
+ maxLength: "10",
9258
+ 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"
9259
+ }
9260
+ ), questDate.type === "Ano" && /* @__PURE__ */ import_react17.default.createElement(
9261
+ "input",
9262
+ {
9263
+ type: "number",
9264
+ placeholder: "AAAA",
9265
+ value: questDate.value || "",
9266
+ onChange: (e) => setQuestDate({ ...questDate, value: e.target.value }),
9267
+ 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"
9268
+ }
9269
+ ), questDate.type === "Date Interval" && /* @__PURE__ */ import_react17.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react17.default.createElement(
9270
+ "input",
9271
+ {
9272
+ type: "number",
9273
+ placeholder: "In\xEDcio (Ano)",
9274
+ value: questDate.start || "",
9275
+ onChange: (e) => setQuestDate({ ...questDate, start: e.target.value }),
9276
+ 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"
9277
+ }
9278
+ ), /* @__PURE__ */ import_react17.default.createElement("span", { className: "text-slate-500" }, "-"), /* @__PURE__ */ import_react17.default.createElement(
9279
+ "input",
9280
+ {
9281
+ type: "number",
9282
+ placeholder: "Fim (Ano)",
9283
+ value: questDate.end || "",
9284
+ onChange: (e) => setQuestDate({ ...questDate, end: e.target.value }),
9285
+ 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"
9286
+ }
9287
+ )))), /* @__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(
9288
+ "button",
9289
+ {
9290
+ type: "button",
9291
+ onClick: () => setRecurrenceConfig((prev) => ({ ...prev, enabled: !prev.enabled })),
9292
+ 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`
9293
+ },
9294
+ /* @__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"}` })
9295
+ )), 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(
9296
+ "select",
9297
+ {
9298
+ value: recurrenceConfig.type,
9299
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, type: e.target.value })),
9300
+ 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"
9301
+ },
9302
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: "daily" }, "Di\xE1ria"),
9303
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: "weekly" }, "Semanal"),
9304
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: "custom" }, "Data Espec\xEDfica")
9305
+ )), 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(
9306
+ "select",
9307
+ {
9308
+ value: recurrenceConfig.day_of_week,
9309
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, day_of_week: parseInt(e.target.value, 10) })),
9310
+ 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"
9311
+ },
9312
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 1 }, "Segunda-feira"),
9313
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 2 }, "Ter\xE7a-feira"),
9314
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 3 }, "Quarta-feira"),
9315
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 4 }, "Quinta-feira"),
9316
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 5 }, "Sexta-feira"),
9317
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 6 }, "S\xE1bado"),
9318
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: 0 }, "Domingo")
9319
+ )), 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(
9320
+ "input",
9321
+ {
9322
+ type: "text",
9323
+ placeholder: "DD/MM/AAAA",
9324
+ value: recurrenceConfig.custom_date,
9325
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, custom_date: e.target.value })),
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__ */ 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(
9329
+ "select",
9330
+ {
9331
+ value: recurrenceConfig.next_assignee_id,
9332
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, next_assignee_id: e.target.value })),
9333
+ 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"
9334
+ },
9335
+ /* @__PURE__ */ import_react17.default.createElement("option", { value: "" }, "Sem respons\xE1vel"),
9336
+ viewMembers.map((member) => /* @__PURE__ */ import_react17.default.createElement("option", { key: member.id, value: member.id }, member.name || member.email))
9337
+ ), /* @__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
9338
  QuestReviewStackEditor,
9109
9339
  {
9110
9340
  reviewStack,
@@ -9889,12 +10119,20 @@ function QuestDetailsPanel({
9889
10119
  const [isSaving, setIsSaving] = (0, import_react20.useState)(false);
9890
10120
  const [isLinkCopied, setIsLinkCopied] = (0, import_react20.useState)(false);
9891
10121
  const [hasUnsavedChanges, setHasUnsavedChanges] = (0, import_react20.useState)(false);
10122
+ const [questDate, setQuestDate] = (0, import_react20.useState)((node == null ? void 0 : node.Date) || null);
9892
10123
  const [noteText, setNoteText] = (0, import_react20.useState)("");
9893
10124
  const [noteLinks, setNoteLinks] = (0, import_react20.useState)([]);
9894
10125
  const [noteImages, setNoteImages] = (0, import_react20.useState)([]);
9895
10126
  const [isNoteFormOpen, setIsNoteFormOpen] = (0, import_react20.useState)(false);
9896
10127
  const [editingEntryId, setEditingEntryId] = (0, import_react20.useState)(null);
9897
10128
  const [editingEntryLabel, setEditingEntryLabel] = (0, import_react20.useState)("");
10129
+ const [recurrenceConfig, setRecurrenceConfig] = (0, import_react20.useState)((node == null ? void 0 : node.recurrence_config) || {
10130
+ enabled: false,
10131
+ type: "daily",
10132
+ day_of_week: 1,
10133
+ custom_date: "",
10134
+ next_assignee_id: ""
10135
+ });
9898
10136
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
9899
10137
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
9900
10138
  initialWidth: isReadMode ? 700 : 440,
@@ -9930,6 +10168,14 @@ function QuestDetailsPanel({
9930
10168
  setCustomProps(extractCustomPropsFromNode(node || {}));
9931
10169
  setReviewStack((node == null ? void 0 : node.reviewStack) || []);
9932
10170
  setTimeline((node == null ? void 0 : node.timeline) || []);
10171
+ setQuestDate((node == null ? void 0 : node.Date) || null);
10172
+ setRecurrenceConfig((node == null ? void 0 : node.recurrence_config) || {
10173
+ enabled: false,
10174
+ type: "daily",
10175
+ day_of_week: 1,
10176
+ custom_date: "",
10177
+ next_assignee_id: ""
10178
+ });
9933
10179
  setHasUnsavedChanges(false);
9934
10180
  }
9935
10181
  }, [node]);
@@ -10025,6 +10271,7 @@ function QuestDetailsPanel({
10025
10271
  setIsSaving(false);
10026
10272
  }
10027
10273
  };
10274
+ const filteredCustomProps = customProps.filter((p) => p.key !== "Date" && p.key !== "Data");
10028
10275
  const handleAddTimelineNote = async () => {
10029
10276
  if (!noteText.trim() && noteLinks.length === 0 && noteImages.length === 0 || isSaving) return;
10030
10277
  setIsSaving(true);
@@ -10181,6 +10428,7 @@ ${noteText}` : noteText : entry.notes,
10181
10428
  const currentTimeline = overrides.timeline !== void 0 ? overrides.timeline : timeline;
10182
10429
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
10183
10430
  const currentStatus = overrides.status !== void 0 ? overrides.status : status;
10431
+ const currentRecurrenceConfig = overrides.recurrenceConfig !== void 0 ? overrides.recurrenceConfig : recurrenceConfig;
10184
10432
  if (!keepOpen && !hasUnsavedChanges) {
10185
10433
  onClose();
10186
10434
  return;
@@ -10204,11 +10452,13 @@ ${noteText}` : noteText : entry.notes,
10204
10452
  status: currentStatus,
10205
10453
  assignee_id: currentAssigneeId || null,
10206
10454
  size,
10207
- description: currentDescription,
10455
+ description: currentDescription.trim(),
10208
10456
  description_sections: processedSections,
10457
+ intensity,
10458
+ recurrence_config: currentRecurrenceConfig,
10459
+ Date: questDate,
10209
10460
  useImageAsTexture: false,
10210
10461
  textureImageUrl: null,
10211
- intensity,
10212
10462
  is_quest: true,
10213
10463
  reviewStack: currentReviewStack,
10214
10464
  timeline: currentTimeline,
@@ -12570,7 +12820,7 @@ function XViewScene({
12570
12820
  }
12571
12821
  }
12572
12822
  Object.values(nodeObjects).forEach((mesh) => {
12573
- const oldLabel = mesh.getObjectByName("timelineLabel");
12823
+ const oldLabel = mesh.getObjectByName("timelineLabel") || mesh.getObjectByName("questDateLabel");
12574
12824
  if (oldLabel) {
12575
12825
  mesh.remove(oldLabel);
12576
12826
  if (oldLabel.material.map) oldLabel.material.map.dispose();
@@ -13003,8 +13253,16 @@ function XViewScene({
13003
13253
  );
13004
13254
  const handleActivateQuestLens = (0, import_react28.useCallback)(({ assigneeId, status: status2 }) => {
13005
13255
  var _a2;
13006
- const { nodeObjects, tweenGroup, graphGroup } = stateRef.current;
13256
+ const { nodeObjects, tweenGroup, graphGroup, timelineIntervalsGroup } = stateRef.current;
13007
13257
  if (!nodeObjects || !tweenGroup || !graphGroup) return;
13258
+ Object.values(nodeObjects).forEach((mesh) => {
13259
+ const oldLabel = mesh.getObjectByName("questDateLabel") || mesh.getObjectByName("timelineLabel");
13260
+ if (oldLabel) {
13261
+ mesh.remove(oldLabel);
13262
+ if (oldLabel.material.map) oldLabel.material.map.dispose();
13263
+ oldLabel.material.dispose();
13264
+ }
13265
+ });
13008
13266
  const allQuests = ((_a2 = sceneDataRef.current) == null ? void 0 : _a2.quest_nodes) || [];
13009
13267
  const filteredQuests = allQuests.filter((q) => {
13010
13268
  const matchAssignee = !assigneeId || String(q.assignee_id) === String(assigneeId);
@@ -13026,6 +13284,60 @@ function XViewScene({
13026
13284
  questsByStatus[q.status].push(q);
13027
13285
  }
13028
13286
  });
13287
+ const parseDate = (str) => {
13288
+ if (!str) return null;
13289
+ const parts = str.split("/");
13290
+ if (parts.length === 3) {
13291
+ const day = parseInt(parts[0], 10);
13292
+ const month = parseInt(parts[1], 10) - 1;
13293
+ const year = parseInt(parts[2], 10);
13294
+ if (!isNaN(day) && !isNaN(month) && !isNaN(year)) {
13295
+ return new Date(year, month, day);
13296
+ }
13297
+ }
13298
+ return null;
13299
+ };
13300
+ const timePoints = /* @__PURE__ */ new Set();
13301
+ const questTimeInfos = /* @__PURE__ */ new Map();
13302
+ filteredQuests.forEach((q) => {
13303
+ const dateProp = q.Date;
13304
+ let info = { isUndated: true, label: "" };
13305
+ if (dateProp) {
13306
+ if (dateProp.date) {
13307
+ const d = parseDate(dateProp.date);
13308
+ if (d) {
13309
+ info = { startDate: d, endDate: d, label: dateProp.date, isUndated: false };
13310
+ timePoints.add(d.getTime());
13311
+ }
13312
+ } else if (dateProp.year) {
13313
+ const y = parseInt(dateProp.year, 10);
13314
+ if (!isNaN(y)) {
13315
+ const d = new Date(y, 0, 1);
13316
+ info = { startDate: d, endDate: d, label: String(y), isUndated: false };
13317
+ timePoints.add(d.getTime());
13318
+ }
13319
+ } else if (dateProp.date_interval && dateProp.date_interval.start) {
13320
+ const sY = parseInt(dateProp.date_interval.start, 10);
13321
+ const eY = parseInt(dateProp.date_interval.end, 10) || sY;
13322
+ if (!isNaN(sY)) {
13323
+ const sd = new Date(sY, 0, 1);
13324
+ const ed = new Date(eY, 11, 31);
13325
+ info = { startDate: sd, endDate: ed, label: `${sY}-${eY}`, isUndated: false };
13326
+ timePoints.add(sd.getTime());
13327
+ timePoints.add(ed.getTime());
13328
+ }
13329
+ }
13330
+ }
13331
+ questTimeInfos.set(String(q.id), info);
13332
+ });
13333
+ const sortedTimePoints = Array.from(timePoints).sort((a, b) => a - b);
13334
+ const timeToYMap = /* @__PURE__ */ new Map();
13335
+ if (sortedTimePoints.length > 0) {
13336
+ const maxIdx = sortedTimePoints.length - 1;
13337
+ sortedTimePoints.forEach((t, i) => {
13338
+ timeToYMap.set(t, (i - maxIdx) * (QUEST_LENS_ROW_SPACING || 30));
13339
+ });
13340
+ }
13029
13341
  const startX = -((QUEST_STATUS_ORDER.length - 1) * QUEST_LENS_COLUMN_SPACING) / 2;
13030
13342
  Object.keys(nodeObjects).forEach((id) => {
13031
13343
  const mesh = nodeObjects[id];
@@ -13062,10 +13374,23 @@ function XViewScene({
13062
13374
  if (!mesh) {
13063
13375
  mesh = addOrUpdateNodeMesh(q, new THREE3.Vector3(colX, 150, 0), true);
13064
13376
  }
13065
- const targetY = (questsInCol.length - 1) * 0.5 * QUEST_LENS_ROW_SPACING - rowIndex * QUEST_LENS_ROW_SPACING;
13377
+ const timeInfo = questTimeInfos.get(nodeId) || { isUndated: true };
13378
+ let targetY;
13379
+ if (!timeInfo.isUndated) {
13380
+ targetY = timeToYMap.get(timeInfo.startDate.getTime());
13381
+ } else {
13382
+ const undatedOffset = sortedTimePoints.length > 0 ? sortedTimePoints.length * 5 : 0;
13383
+ targetY = -80 - rowIndex * (QUEST_LENS_ROW_SPACING || 30) - undatedOffset;
13384
+ }
13066
13385
  const lensTween = new import_tween2.Tween(mesh.position).to({ x: colX, y: targetY, z: 0 }, 1500).easing(import_tween2.Easing.Cubic.InOut);
13067
13386
  tweenGroup.add(lensTween);
13068
13387
  lensTween.start();
13388
+ if (!timeInfo.isUndated && timeInfo.label) {
13389
+ const dateSprite = createTextSprite(timeInfo.label, 36);
13390
+ dateSprite.name = "questDateLabel";
13391
+ dateSprite.position.set(0, 3.5, 0);
13392
+ mesh.add(dateSprite);
13393
+ }
13069
13394
  if (mesh.userData.labelObject) {
13070
13395
  const labelTargetPos = new THREE3.Vector3(colX, targetY, 0).add(
13071
13396
  mesh.userData.labelOffset || new THREE3.Vector3(0, -2.5, 0)
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,67 @@ 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
+ reviewStack: (nodeToSave.reviewStack || []).map((r) => ({ ...r, status: "pending" })),
2885
+ recurrence_config: nextRecurrenceConfig
2886
+ };
2887
+ specificParentData.nodes.push(nextQuestNode);
2888
+ if (stateRef.current.nodeObjects[String(nodeToSave.id)]) {
2889
+ const pos = stateRef.current.nodeObjects[String(nodeToSave.id)].position.clone();
2890
+ pos.x += 12;
2891
+ addStandaloneNodeToScene(stateRef.current, nextQuestNode, pos);
2892
+ }
2893
+ }
2894
+ }
2843
2895
  try {
2844
- const isView = ((_a = context.viewType) == null ? void 0 : _a.toLowerCase()) === "view";
2896
+ const isView = ((_c = context.viewType) == null ? void 0 : _c.toLowerCase()) === "view";
2845
2897
  if (isView && parentFileId === context.sceneConfigId) {
2846
2898
  const visualNodeIndex = sceneDataRef.current.nodes.findIndex((n) => String(n.id) === String(nodeToSave.id));
2847
2899
  if (visualNodeIndex > -1) {
@@ -3235,6 +3287,8 @@ var IGNORED_KEYS = [
3235
3287
  "is_quest",
3236
3288
  "raw_title",
3237
3289
  "assignee_id",
3290
+ "Date",
3291
+ "recurrence_config",
3238
3292
  "reviewStack",
3239
3293
  "timeline"
3240
3294
  ];
@@ -3697,10 +3751,44 @@ function createTimelineEntry(actorId, description, type = "comment", extras = {}
3697
3751
  id: img.id || v4_default(),
3698
3752
  name: img.name || "",
3699
3753
  value: img.value || ""
3700
- })),
3701
- metadata
3754
+ }))
3702
3755
  };
3703
3756
  }
3757
+ function calculateNextRecurrenceDate(config) {
3758
+ if (!config || !config.enabled) return null;
3759
+ const now = /* @__PURE__ */ new Date();
3760
+ let nextDate = new Date(now);
3761
+ switch (config.type) {
3762
+ case "daily":
3763
+ nextDate.setDate(now.getDate() + 1);
3764
+ break;
3765
+ case "weekly":
3766
+ const targetDay = parseInt(config.day_of_week, 10);
3767
+ const currentDay = now.getDay();
3768
+ let daysUntilNext = targetDay - currentDay;
3769
+ if (daysUntilNext <= 0) daysUntilNext += 7;
3770
+ nextDate.setDate(now.getDate() + daysUntilNext);
3771
+ break;
3772
+ case "custom":
3773
+ if (config.custom_date) {
3774
+ const parts = config.custom_date.split("/");
3775
+ if (parts.length === 3) {
3776
+ nextDate = new Date(parseInt(parts[2], 10), parseInt(parts[1], 10) - 1, parseInt(parts[0], 10));
3777
+ } else {
3778
+ return null;
3779
+ }
3780
+ } else {
3781
+ return null;
3782
+ }
3783
+ break;
3784
+ default:
3785
+ return null;
3786
+ }
3787
+ const d = String(nextDate.getDate()).padStart(2, "0");
3788
+ const m = String(nextDate.getMonth() + 1).padStart(2, "0");
3789
+ const y = nextDate.getFullYear();
3790
+ return { date: `${d}/${m}/${y}` };
3791
+ }
3704
3792
 
3705
3793
  // src/components/CustomPropertyDisplay.jsx
3706
3794
  import React3, { useState as useState4, useRef as useRef3, useEffect as useEffect3 } from "react";
@@ -8684,7 +8772,9 @@ import {
8684
8772
  FiX as FiX5,
8685
8773
  FiChevronDown as FiChevronDown6,
8686
8774
  FiUser as FiUser2,
8687
- FiSearch as FiSearch5
8775
+ FiSearch as FiSearch5,
8776
+ FiCalendar,
8777
+ FiRepeat
8688
8778
  } from "react-icons/fi";
8689
8779
 
8690
8780
  // src/components/QuestReviewStackEditor.jsx
@@ -8883,7 +8973,15 @@ function InSceneQuestForm({
8883
8973
  const [size, setSize] = useState17("medium");
8884
8974
  const [intensity, setIntensity] = useState17(0);
8885
8975
  const [description, setDescription] = useState17("");
8976
+ const [recurrenceConfig, setRecurrenceConfig] = useState17({
8977
+ enabled: false,
8978
+ type: "daily",
8979
+ day_of_week: 1,
8980
+ custom_date: "",
8981
+ next_assignee_id: ""
8982
+ });
8886
8983
  const [reviewStack, setReviewStack] = useState17([]);
8984
+ const [questDate, setQuestDate] = useState17(null);
8887
8985
  const [isStatusDropdownOpen, setIsStatusDropdownOpen] = useState17(false);
8888
8986
  const [isAssigneeDropdownOpen, setIsAssigneeDropdownOpen] = useState17(false);
8889
8987
  const [assigneeSearchQuery, setAssigneeSearchQuery] = useState17("");
@@ -8955,6 +9053,8 @@ function InSceneQuestForm({
8955
9053
  textureImageUrl: null,
8956
9054
  reviewStack,
8957
9055
  timeline: initialTimeline,
9056
+ Date: questDate,
9057
+ recurrence_config: recurrenceConfig,
8958
9058
  ...additionalData
8959
9059
  });
8960
9060
  };
@@ -9121,7 +9221,139 @@ function InSceneQuestForm({
9121
9221
  )), viewMembers.filter((member) => {
9122
9222
  const search = assigneeSearchQuery.toLowerCase();
9123
9223
  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(
9224
+ }).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(
9225
+ "button",
9226
+ {
9227
+ type: "button",
9228
+ onClick: () => setQuestDate({ type: "Data", value: "" }),
9229
+ className: "text-[10px] text-indigo-400 hover:text-indigo-300 font-bold uppercase transition-colors"
9230
+ },
9231
+ "+ Adicionar"
9232
+ )), 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(
9233
+ "button",
9234
+ {
9235
+ type: "button",
9236
+ onClick: () => setQuestDate(null),
9237
+ 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",
9238
+ title: "Remover data"
9239
+ },
9240
+ /* @__PURE__ */ React16.createElement(FiX5, { size: 12 })
9241
+ ), /* @__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(
9242
+ "button",
9243
+ {
9244
+ key: t,
9245
+ type: "button",
9246
+ onClick: () => {
9247
+ const newDate = { type: t };
9248
+ if (t === "Date Interval") {
9249
+ newDate.start = "";
9250
+ newDate.end = "";
9251
+ } else {
9252
+ newDate.value = "";
9253
+ }
9254
+ setQuestDate(newDate);
9255
+ },
9256
+ 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"}`
9257
+ },
9258
+ t === "Date Interval" ? "Intervalo" : t
9259
+ ))), questDate.type === "Data" && /* @__PURE__ */ React16.createElement(
9260
+ "input",
9261
+ {
9262
+ type: "text",
9263
+ placeholder: "DD/MM/AAAA",
9264
+ value: questDate.value || "",
9265
+ onChange: (e) => {
9266
+ const rawValue = e.target.value;
9267
+ const digits = rawValue.replace(/\D/g, "");
9268
+ const len = digits.length;
9269
+ let formattedValue = digits;
9270
+ if (len > 2 && len <= 4)
9271
+ formattedValue = `${digits.slice(0, 2)}/${digits.slice(2)}`;
9272
+ else if (len > 4)
9273
+ formattedValue = `${digits.slice(0, 2)}/${digits.slice(2, 4)}/${digits.slice(4, 8)}`;
9274
+ setQuestDate({ ...questDate, value: formattedValue });
9275
+ },
9276
+ maxLength: "10",
9277
+ 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"
9278
+ }
9279
+ ), questDate.type === "Ano" && /* @__PURE__ */ React16.createElement(
9280
+ "input",
9281
+ {
9282
+ type: "number",
9283
+ placeholder: "AAAA",
9284
+ value: questDate.value || "",
9285
+ onChange: (e) => setQuestDate({ ...questDate, value: e.target.value }),
9286
+ 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"
9287
+ }
9288
+ ), questDate.type === "Date Interval" && /* @__PURE__ */ React16.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React16.createElement(
9289
+ "input",
9290
+ {
9291
+ type: "number",
9292
+ placeholder: "In\xEDcio (Ano)",
9293
+ value: questDate.start || "",
9294
+ onChange: (e) => setQuestDate({ ...questDate, start: e.target.value }),
9295
+ 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"
9296
+ }
9297
+ ), /* @__PURE__ */ React16.createElement("span", { className: "text-slate-500" }, "-"), /* @__PURE__ */ React16.createElement(
9298
+ "input",
9299
+ {
9300
+ type: "number",
9301
+ placeholder: "Fim (Ano)",
9302
+ value: questDate.end || "",
9303
+ onChange: (e) => setQuestDate({ ...questDate, end: e.target.value }),
9304
+ 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"
9305
+ }
9306
+ )))), /* @__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(
9307
+ "button",
9308
+ {
9309
+ type: "button",
9310
+ onClick: () => setRecurrenceConfig((prev) => ({ ...prev, enabled: !prev.enabled })),
9311
+ 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`
9312
+ },
9313
+ /* @__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"}` })
9314
+ )), 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(
9315
+ "select",
9316
+ {
9317
+ value: recurrenceConfig.type,
9318
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, type: e.target.value })),
9319
+ 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"
9320
+ },
9321
+ /* @__PURE__ */ React16.createElement("option", { value: "daily" }, "Di\xE1ria"),
9322
+ /* @__PURE__ */ React16.createElement("option", { value: "weekly" }, "Semanal"),
9323
+ /* @__PURE__ */ React16.createElement("option", { value: "custom" }, "Data Espec\xEDfica")
9324
+ )), 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(
9325
+ "select",
9326
+ {
9327
+ value: recurrenceConfig.day_of_week,
9328
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, day_of_week: parseInt(e.target.value, 10) })),
9329
+ 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"
9330
+ },
9331
+ /* @__PURE__ */ React16.createElement("option", { value: 1 }, "Segunda-feira"),
9332
+ /* @__PURE__ */ React16.createElement("option", { value: 2 }, "Ter\xE7a-feira"),
9333
+ /* @__PURE__ */ React16.createElement("option", { value: 3 }, "Quarta-feira"),
9334
+ /* @__PURE__ */ React16.createElement("option", { value: 4 }, "Quinta-feira"),
9335
+ /* @__PURE__ */ React16.createElement("option", { value: 5 }, "Sexta-feira"),
9336
+ /* @__PURE__ */ React16.createElement("option", { value: 6 }, "S\xE1bado"),
9337
+ /* @__PURE__ */ React16.createElement("option", { value: 0 }, "Domingo")
9338
+ )), 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(
9339
+ "input",
9340
+ {
9341
+ type: "text",
9342
+ placeholder: "DD/MM/AAAA",
9343
+ value: recurrenceConfig.custom_date,
9344
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, custom_date: e.target.value })),
9345
+ 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"
9346
+ }
9347
+ ))), /* @__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(
9348
+ "select",
9349
+ {
9350
+ value: recurrenceConfig.next_assignee_id,
9351
+ onChange: (e) => setRecurrenceConfig((prev) => ({ ...prev, next_assignee_id: e.target.value })),
9352
+ 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"
9353
+ },
9354
+ /* @__PURE__ */ React16.createElement("option", { value: "" }, "Sem respons\xE1vel"),
9355
+ viewMembers.map((member) => /* @__PURE__ */ React16.createElement("option", { key: member.id, value: member.id }, member.name || member.email))
9356
+ ), /* @__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
9357
  QuestReviewStackEditor,
9126
9358
  {
9127
9359
  reviewStack,
@@ -9776,7 +10008,7 @@ function NodeDetailsPanel({
9776
10008
 
9777
10009
  // src/components/QuestDetailsPanel.jsx
9778
10010
  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";
10011
+ 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
10012
 
9781
10013
  // src/components/QuestTimeline.jsx
9782
10014
  import React18 from "react";
@@ -9918,12 +10150,20 @@ function QuestDetailsPanel({
9918
10150
  const [isSaving, setIsSaving] = useState19(false);
9919
10151
  const [isLinkCopied, setIsLinkCopied] = useState19(false);
9920
10152
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState19(false);
10153
+ const [questDate, setQuestDate] = useState19((node == null ? void 0 : node.Date) || null);
9921
10154
  const [noteText, setNoteText] = useState19("");
9922
10155
  const [noteLinks, setNoteLinks] = useState19([]);
9923
10156
  const [noteImages, setNoteImages] = useState19([]);
9924
10157
  const [isNoteFormOpen, setIsNoteFormOpen] = useState19(false);
9925
10158
  const [editingEntryId, setEditingEntryId] = useState19(null);
9926
10159
  const [editingEntryLabel, setEditingEntryLabel] = useState19("");
10160
+ const [recurrenceConfig, setRecurrenceConfig] = useState19((node == null ? void 0 : node.recurrence_config) || {
10161
+ enabled: false,
10162
+ type: "daily",
10163
+ day_of_week: 1,
10164
+ custom_date: "",
10165
+ next_assignee_id: ""
10166
+ });
9927
10167
  const maxPanelW = typeof window !== "undefined" ? window.innerWidth * 0.92 : 1200;
9928
10168
  const { width: panelWidth, isResizing, handlePointerDown: handleResize, setWidth } = useResizablePanel({
9929
10169
  initialWidth: isReadMode ? 700 : 440,
@@ -9959,6 +10199,14 @@ function QuestDetailsPanel({
9959
10199
  setCustomProps(extractCustomPropsFromNode(node || {}));
9960
10200
  setReviewStack((node == null ? void 0 : node.reviewStack) || []);
9961
10201
  setTimeline((node == null ? void 0 : node.timeline) || []);
10202
+ setQuestDate((node == null ? void 0 : node.Date) || null);
10203
+ setRecurrenceConfig((node == null ? void 0 : node.recurrence_config) || {
10204
+ enabled: false,
10205
+ type: "daily",
10206
+ day_of_week: 1,
10207
+ custom_date: "",
10208
+ next_assignee_id: ""
10209
+ });
9962
10210
  setHasUnsavedChanges(false);
9963
10211
  }
9964
10212
  }, [node]);
@@ -10054,6 +10302,7 @@ function QuestDetailsPanel({
10054
10302
  setIsSaving(false);
10055
10303
  }
10056
10304
  };
10305
+ const filteredCustomProps = customProps.filter((p) => p.key !== "Date" && p.key !== "Data");
10057
10306
  const handleAddTimelineNote = async () => {
10058
10307
  if (!noteText.trim() && noteLinks.length === 0 && noteImages.length === 0 || isSaving) return;
10059
10308
  setIsSaving(true);
@@ -10210,6 +10459,7 @@ ${noteText}` : noteText : entry.notes,
10210
10459
  const currentTimeline = overrides.timeline !== void 0 ? overrides.timeline : timeline;
10211
10460
  const currentExistingSections = overrides.existingSections !== void 0 ? overrides.existingSections : existingSections;
10212
10461
  const currentStatus = overrides.status !== void 0 ? overrides.status : status;
10462
+ const currentRecurrenceConfig = overrides.recurrenceConfig !== void 0 ? overrides.recurrenceConfig : recurrenceConfig;
10213
10463
  if (!keepOpen && !hasUnsavedChanges) {
10214
10464
  onClose();
10215
10465
  return;
@@ -10233,11 +10483,13 @@ ${noteText}` : noteText : entry.notes,
10233
10483
  status: currentStatus,
10234
10484
  assignee_id: currentAssigneeId || null,
10235
10485
  size,
10236
- description: currentDescription,
10486
+ description: currentDescription.trim(),
10237
10487
  description_sections: processedSections,
10488
+ intensity,
10489
+ recurrence_config: currentRecurrenceConfig,
10490
+ Date: questDate,
10238
10491
  useImageAsTexture: false,
10239
10492
  textureImageUrl: null,
10240
- intensity,
10241
10493
  is_quest: true,
10242
10494
  reviewStack: currentReviewStack,
10243
10495
  timeline: currentTimeline,
@@ -12612,7 +12864,7 @@ function XViewScene({
12612
12864
  }
12613
12865
  }
12614
12866
  Object.values(nodeObjects).forEach((mesh) => {
12615
- const oldLabel = mesh.getObjectByName("timelineLabel");
12867
+ const oldLabel = mesh.getObjectByName("timelineLabel") || mesh.getObjectByName("questDateLabel");
12616
12868
  if (oldLabel) {
12617
12869
  mesh.remove(oldLabel);
12618
12870
  if (oldLabel.material.map) oldLabel.material.map.dispose();
@@ -13045,8 +13297,16 @@ function XViewScene({
13045
13297
  );
13046
13298
  const handleActivateQuestLens = useCallback4(({ assigneeId, status: status2 }) => {
13047
13299
  var _a2;
13048
- const { nodeObjects, tweenGroup, graphGroup } = stateRef.current;
13300
+ const { nodeObjects, tweenGroup, graphGroup, timelineIntervalsGroup } = stateRef.current;
13049
13301
  if (!nodeObjects || !tweenGroup || !graphGroup) return;
13302
+ Object.values(nodeObjects).forEach((mesh) => {
13303
+ const oldLabel = mesh.getObjectByName("questDateLabel") || mesh.getObjectByName("timelineLabel");
13304
+ if (oldLabel) {
13305
+ mesh.remove(oldLabel);
13306
+ if (oldLabel.material.map) oldLabel.material.map.dispose();
13307
+ oldLabel.material.dispose();
13308
+ }
13309
+ });
13050
13310
  const allQuests = ((_a2 = sceneDataRef.current) == null ? void 0 : _a2.quest_nodes) || [];
13051
13311
  const filteredQuests = allQuests.filter((q) => {
13052
13312
  const matchAssignee = !assigneeId || String(q.assignee_id) === String(assigneeId);
@@ -13068,6 +13328,60 @@ function XViewScene({
13068
13328
  questsByStatus[q.status].push(q);
13069
13329
  }
13070
13330
  });
13331
+ const parseDate = (str) => {
13332
+ if (!str) return null;
13333
+ const parts = str.split("/");
13334
+ if (parts.length === 3) {
13335
+ const day = parseInt(parts[0], 10);
13336
+ const month = parseInt(parts[1], 10) - 1;
13337
+ const year = parseInt(parts[2], 10);
13338
+ if (!isNaN(day) && !isNaN(month) && !isNaN(year)) {
13339
+ return new Date(year, month, day);
13340
+ }
13341
+ }
13342
+ return null;
13343
+ };
13344
+ const timePoints = /* @__PURE__ */ new Set();
13345
+ const questTimeInfos = /* @__PURE__ */ new Map();
13346
+ filteredQuests.forEach((q) => {
13347
+ const dateProp = q.Date;
13348
+ let info = { isUndated: true, label: "" };
13349
+ if (dateProp) {
13350
+ if (dateProp.date) {
13351
+ const d = parseDate(dateProp.date);
13352
+ if (d) {
13353
+ info = { startDate: d, endDate: d, label: dateProp.date, isUndated: false };
13354
+ timePoints.add(d.getTime());
13355
+ }
13356
+ } else if (dateProp.year) {
13357
+ const y = parseInt(dateProp.year, 10);
13358
+ if (!isNaN(y)) {
13359
+ const d = new Date(y, 0, 1);
13360
+ info = { startDate: d, endDate: d, label: String(y), isUndated: false };
13361
+ timePoints.add(d.getTime());
13362
+ }
13363
+ } else if (dateProp.date_interval && dateProp.date_interval.start) {
13364
+ const sY = parseInt(dateProp.date_interval.start, 10);
13365
+ const eY = parseInt(dateProp.date_interval.end, 10) || sY;
13366
+ if (!isNaN(sY)) {
13367
+ const sd = new Date(sY, 0, 1);
13368
+ const ed = new Date(eY, 11, 31);
13369
+ info = { startDate: sd, endDate: ed, label: `${sY}-${eY}`, isUndated: false };
13370
+ timePoints.add(sd.getTime());
13371
+ timePoints.add(ed.getTime());
13372
+ }
13373
+ }
13374
+ }
13375
+ questTimeInfos.set(String(q.id), info);
13376
+ });
13377
+ const sortedTimePoints = Array.from(timePoints).sort((a, b) => a - b);
13378
+ const timeToYMap = /* @__PURE__ */ new Map();
13379
+ if (sortedTimePoints.length > 0) {
13380
+ const maxIdx = sortedTimePoints.length - 1;
13381
+ sortedTimePoints.forEach((t, i) => {
13382
+ timeToYMap.set(t, (i - maxIdx) * (QUEST_LENS_ROW_SPACING || 30));
13383
+ });
13384
+ }
13071
13385
  const startX = -((QUEST_STATUS_ORDER.length - 1) * QUEST_LENS_COLUMN_SPACING) / 2;
13072
13386
  Object.keys(nodeObjects).forEach((id) => {
13073
13387
  const mesh = nodeObjects[id];
@@ -13104,10 +13418,23 @@ function XViewScene({
13104
13418
  if (!mesh) {
13105
13419
  mesh = addOrUpdateNodeMesh(q, new THREE3.Vector3(colX, 150, 0), true);
13106
13420
  }
13107
- const targetY = (questsInCol.length - 1) * 0.5 * QUEST_LENS_ROW_SPACING - rowIndex * QUEST_LENS_ROW_SPACING;
13421
+ const timeInfo = questTimeInfos.get(nodeId) || { isUndated: true };
13422
+ let targetY;
13423
+ if (!timeInfo.isUndated) {
13424
+ targetY = timeToYMap.get(timeInfo.startDate.getTime());
13425
+ } else {
13426
+ const undatedOffset = sortedTimePoints.length > 0 ? sortedTimePoints.length * 5 : 0;
13427
+ targetY = -80 - rowIndex * (QUEST_LENS_ROW_SPACING || 30) - undatedOffset;
13428
+ }
13108
13429
  const lensTween = new Tween2(mesh.position).to({ x: colX, y: targetY, z: 0 }, 1500).easing(Easing2.Cubic.InOut);
13109
13430
  tweenGroup.add(lensTween);
13110
13431
  lensTween.start();
13432
+ if (!timeInfo.isUndated && timeInfo.label) {
13433
+ const dateSprite = createTextSprite(timeInfo.label, 36);
13434
+ dateSprite.name = "questDateLabel";
13435
+ dateSprite.position.set(0, 3.5, 0);
13436
+ mesh.add(dateSprite);
13437
+ }
13111
13438
  if (mesh.userData.labelObject) {
13112
13439
  const labelTargetPos = new THREE3.Vector3(colX, targetY, 0).add(
13113
13440
  mesh.userData.labelOffset || new THREE3.Vector3(0, -2.5, 0)
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.37",
3
+ "version": "1.2.5-dev.39",
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",