@@ -176,6 +176,7 @@ const updateTaskStateMutation = graphql`
|
||||
task {
|
||||
id
|
||||
state
|
||||
timeEstimate
|
||||
version
|
||||
}
|
||||
}
|
||||
@@ -1178,6 +1179,124 @@ function ControlViewContent({
|
||||
setSearchParams(searchParams);
|
||||
};
|
||||
|
||||
// Add state variables for tracking edit mode and duration components
|
||||
const [isEditingDuration, setIsEditingDuration] = useState(false);
|
||||
const [editTimeEstimateDays, setEditTimeEstimateDays] = useState("");
|
||||
const [editTimeEstimateHours, setEditTimeEstimateHours] = useState("");
|
||||
const [editTimeEstimateMinutes, setEditTimeEstimateMinutes] = useState("");
|
||||
|
||||
// Function to parse ISO duration string into components for editing
|
||||
const parseISODuration = useCallback(
|
||||
(duration: string | null | undefined) => {
|
||||
if (!duration || !duration.startsWith("P")) {
|
||||
return { days: "", hours: "", minutes: "" };
|
||||
}
|
||||
|
||||
try {
|
||||
const durationRegex =
|
||||
/P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?/;
|
||||
const matches = duration.match(durationRegex);
|
||||
|
||||
if (!matches) return { days: "", hours: "", minutes: "" };
|
||||
|
||||
// We only care about days, hours, and minutes
|
||||
const days = matches[3] ? matches[3] : "";
|
||||
const hours = matches[4] ? matches[4] : "";
|
||||
const minutes = matches[5] ? matches[5] : "";
|
||||
|
||||
return { days, hours, minutes };
|
||||
} catch (error) {
|
||||
console.error("Error parsing duration:", error);
|
||||
return { days: "", hours: "", minutes: "" };
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Function to handle saving the updated duration
|
||||
const handleSaveDuration = useCallback(
|
||||
(taskId: string, version: number) => {
|
||||
// Convert to ISO duration format
|
||||
let duration = "P";
|
||||
|
||||
if (editTimeEstimateDays && parseInt(editTimeEstimateDays) > 0) {
|
||||
duration += `${parseInt(editTimeEstimateDays)}D`;
|
||||
}
|
||||
|
||||
if (
|
||||
(editTimeEstimateHours && parseInt(editTimeEstimateHours) > 0) ||
|
||||
(editTimeEstimateMinutes && parseInt(editTimeEstimateMinutes) > 0)
|
||||
) {
|
||||
duration += "T";
|
||||
|
||||
if (editTimeEstimateHours && parseInt(editTimeEstimateHours) > 0) {
|
||||
duration += `${parseInt(editTimeEstimateHours)}H`;
|
||||
}
|
||||
|
||||
if (editTimeEstimateMinutes && parseInt(editTimeEstimateMinutes) > 0) {
|
||||
duration += `${parseInt(editTimeEstimateMinutes)}M`;
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid time components were provided, use null (remove the time estimate)
|
||||
const timeEstimate = duration === "P" ? null : duration;
|
||||
|
||||
updateTask({
|
||||
variables: {
|
||||
input: {
|
||||
taskId,
|
||||
timeEstimate,
|
||||
expectedVersion: version,
|
||||
},
|
||||
},
|
||||
onCompleted: () => {
|
||||
toast({
|
||||
title: "Task updated",
|
||||
description: "Time estimate has been updated successfully.",
|
||||
});
|
||||
setIsEditingDuration(false);
|
||||
|
||||
// Update the selected task state if it's the current task
|
||||
if (selectedTask && selectedTask.id === taskId) {
|
||||
setSelectedTask({
|
||||
...selectedTask,
|
||||
timeEstimate,
|
||||
version: version + 1,
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast({
|
||||
title: "Error updating task",
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
[
|
||||
editTimeEstimateDays,
|
||||
editTimeEstimateHours,
|
||||
editTimeEstimateMinutes,
|
||||
updateTask,
|
||||
toast,
|
||||
selectedTask,
|
||||
setSelectedTask,
|
||||
]
|
||||
);
|
||||
|
||||
// Function to start editing duration
|
||||
const startEditingDuration = useCallback(
|
||||
(duration: string | null | undefined) => {
|
||||
const { days, hours, minutes } = parseISODuration(duration);
|
||||
setEditTimeEstimateDays(days);
|
||||
setEditTimeEstimateHours(hours);
|
||||
setEditTimeEstimateMinutes(minutes);
|
||||
setIsEditingDuration(true);
|
||||
},
|
||||
[parseISODuration]
|
||||
);
|
||||
|
||||
return (
|
||||
<PageTemplate
|
||||
title={data.control.name ?? ""}
|
||||
@@ -1824,10 +1943,80 @@ function ControlViewContent({
|
||||
? "Completed"
|
||||
: "In Progress"}
|
||||
</div>
|
||||
{selectedTask.timeEstimate && (
|
||||
<div className="text-sm text-gray-500 flex items-center">
|
||||
{!isEditingDuration ? (
|
||||
<div
|
||||
className="text-sm text-gray-500 flex items-center hover:bg-gray-100 px-2 py-1 rounded-md cursor-pointer"
|
||||
onClick={() =>
|
||||
startEditingDuration(selectedTask.timeEstimate)
|
||||
}
|
||||
>
|
||||
<span className="inline-block w-4 h-4 mr-1">⏱️</span>
|
||||
<span>{formatDuration(selectedTask.timeEstimate)}</span>
|
||||
<span>
|
||||
{selectedTask.timeEstimate
|
||||
? formatDuration(selectedTask.timeEstimate)
|
||||
: "Add time estimate"}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
value={editTimeEstimateDays}
|
||||
onChange={(e) =>
|
||||
setEditTimeEstimateDays(e.target.value)
|
||||
}
|
||||
className="w-12 p-1 text-xs border rounded"
|
||||
placeholder="0"
|
||||
/>
|
||||
<span className="text-xs text-gray-500">d</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="23"
|
||||
value={editTimeEstimateHours}
|
||||
onChange={(e) =>
|
||||
setEditTimeEstimateHours(e.target.value)
|
||||
}
|
||||
className="w-12 p-1 text-xs border rounded"
|
||||
placeholder="0"
|
||||
/>
|
||||
<span className="text-xs text-gray-500">h</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="59"
|
||||
value={editTimeEstimateMinutes}
|
||||
onChange={(e) =>
|
||||
setEditTimeEstimateMinutes(e.target.value)
|
||||
}
|
||||
className="w-12 p-1 text-xs border rounded"
|
||||
placeholder="0"
|
||||
/>
|
||||
<span className="text-xs text-gray-500">m</span>
|
||||
</div>
|
||||
<button
|
||||
className="p-1 text-sm text-blue-600 hover:text-blue-800"
|
||||
onClick={() =>
|
||||
handleSaveDuration(
|
||||
selectedTask.id,
|
||||
selectedTask.version
|
||||
)
|
||||
}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
className="p-1 text-sm text-gray-500 hover:text-gray-700"
|
||||
onClick={() => setIsEditingDuration(false)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<70ae17b69b366175da4d00bfae78ae7f>>
|
||||
* @generated SignedSource<<f0b91a291608cee99d4b22fb2d19e847>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -26,6 +26,7 @@ export type ControlViewUpdateTaskStateMutation$data = {
|
||||
readonly task: {
|
||||
readonly id: string;
|
||||
readonly state: TaskState;
|
||||
readonly timeEstimate: any | null | undefined;
|
||||
readonly version: number;
|
||||
};
|
||||
};
|
||||
@@ -80,6 +81,13 @@ v1 = [
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "timeEstimate",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -112,16 +120,16 @@ return {
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "1b9a74365007e7c46668815c9e37cd0a",
|
||||
"cacheID": "f8f00bd63de4f5eac131954a8039b40e",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "ControlViewUpdateTaskStateMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation ControlViewUpdateTaskStateMutation(\n $input: UpdateTaskInput!\n) {\n updateTask(input: $input) {\n task {\n id\n state\n version\n }\n }\n}\n"
|
||||
"text": "mutation ControlViewUpdateTaskStateMutation(\n $input: UpdateTaskInput!\n) {\n updateTask(input: $input) {\n task {\n id\n state\n timeEstimate\n version\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "7cb2f42aadcc5f709377d2bf9dfea2da";
|
||||
(node as any).hash = "f53df99b24c2f5f233f8d865885718ea";
|
||||
|
||||
export default node;
|
||||
|
||||
Reference in New Issue
Block a user