Add deadline on task

Signed-off-by: Andreas Reußner <andreas.reussner@outlook.de>
This commit is contained in:
Andreas Reußner
2025-06-02 10:47:26 +02:00
committed by Sacha Al Himdani
parent 02ff3b02d9
commit fc008c27f4
16 changed files with 343 additions and 33 deletions

View File

@@ -109,6 +109,7 @@ import { MeasureViewRisksQuery } from "./__generated__/MeasureViewRisksQuery.gra
import { MeasureViewDeleteMeasureMutation } from "./__generated__/MeasureViewDeleteMeasureMutation.graphql";
import remarkGfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import { format, formatISO, parseISO } from "date-fns";
// Function to format ISO8601 duration to human-readable format
const formatDuration = (isoDuration: string): string => {
@@ -181,6 +182,7 @@ const measureViewQuery = graphql`
description
state
timeEstimate
deadline
assignedTo {
id
fullName
@@ -218,6 +220,7 @@ const updateTaskStateMutation = graphql`
id
state
timeEstimate
deadline
}
}
}
@@ -235,6 +238,7 @@ const createTaskMutation = graphql`
name
description
timeEstimate
deadline
state
assignedTo {
id
@@ -743,6 +747,7 @@ function MeasureViewContent({
const [isCreateTaskOpen, setIsCreateTaskOpen] = useState(false);
const [newTaskName, setNewTaskName] = useState("");
const [newTaskDescription, setNewTaskDescription] = useState("");
const [newDeadline, setNewDeadline] = useState("");
const [timeEstimateDays, setTimeEstimateDays] = useState("");
const [timeEstimateHours, setTimeEstimateHours] = useState("");
const [timeEstimateMinutes, setTimeEstimateMinutes] = useState("");
@@ -991,6 +996,7 @@ function MeasureViewContent({
name: newTaskName,
description: newTaskDescription,
timeEstimate: isoTimeEstimate === "" ? null : isoTimeEstimate,
deadline: newDeadline === "" ? null : formatISO(newDeadline),
},
},
onCompleted: () => {
@@ -1004,6 +1010,7 @@ function MeasureViewContent({
setTimeEstimateHours("");
setTimeEstimateMinutes("");
setIsCreateTaskOpen(false);
setNewDeadline("");
},
onError: (error) => {
toast({
@@ -1718,9 +1725,11 @@ function MeasureViewContent({
// Add state variables for tracking edit mode and duration components
const [isEditingDuration, setIsEditingDuration] = useState(false);
const [isEditingDeadline, setIsEditingDeadline] = useState(false);
const [editTimeEstimateDays, setEditTimeEstimateDays] = useState("");
const [editTimeEstimateHours, setEditTimeEstimateHours] = useState("");
const [editTimeEstimateMinutes, setEditTimeEstimateMinutes] = useState("");
const [editDeadline, setEditDeadline] = useState("");
// Function to parse ISO duration string into components for editing
const parseISODuration = useCallback(
@@ -1750,6 +1759,40 @@ function MeasureViewContent({
[]
);
// Function to handle saving the updated deadline
const handleSaveDeadline = useCallback(
(taskId: string) => {
const newDeadline = editDeadline === "" ? null : formatISO(editDeadline);
updateTask({
variables: {
input: {
taskId,
deadline: newDeadline,
},
},
onCompleted: () => {
setIsEditingDeadline(false);
// Update the selected task state if it's the current task
if (selectedTask && selectedTask.id === taskId) {
setSelectedTask({
...selectedTask,
deadline: newDeadline,
});
}
},
onError: (error) => {
toast({
title: "Error updating task",
description: error.message,
variant: "destructive",
});
},
});
},
[editDeadline, updateTask, toast, selectedTask, setSelectedTask]
);
// Function to handle saving the updated duration
const handleSaveDuration = useCallback(
(taskId: string) => {
@@ -2605,6 +2648,11 @@ function MeasureViewContent({
{formatDuration(task.timeEstimate)}
</div>
)}
{task.deadline && (
<div className="text-sm text-secondary">
{formatDate(task.deadline)}
</div>
)}
</div>
</div>
@@ -3063,6 +3111,69 @@ function MeasureViewContent({
)}
</div>
{/* Deadline */}
<div>
<h3 className="text-sm font-medium text-secondary mb-2">
Deadline
</h3>
{isEditingDeadline ? (
<div className="space-y-2">
<div className="flex gap-2">
<div className="flex-1">
<Input
type="date"
value={editDeadline}
onChange={(e) =>
setEditDeadline(e.target.value)
}
/>
</div>
</div>
<div className="flex justify-end gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setIsEditingDeadline(false)}
>
Cancel
</Button>
<Button
size="sm"
onClick={() => handleSaveDeadline(selectedTask.id)}
>
Save
</Button>
</div>
</div>
) : (
<div className="flex items-center justify-between">
<div>
{selectedTask.deadline ? (
<span>
{formatDate(selectedTask.deadline)}
</span>
) : (
<span className="text-secondary">
No deadline
</span>
)}
</div>
<Button
variant="ghost"
size="sm"
className="p-1 h-auto"
onClick={() => {
const selectedDeadline = selectedTask.deadline ? format(selectedTask.deadline, 'yyyy-MM-dd') : "";
setEditDeadline(selectedDeadline);
setIsEditingDeadline(true);
}}
>
Edit
</Button>
</div>
)}
</div>
{/* Evidence section */}
<div>
<div className="flex items-center justify-between mb-3">
@@ -3846,6 +3957,15 @@ function MeasureViewContent({
</div>
</div>
</div>
<div className="grid w-full items-center gap-1.5">
<Label htmlFor="task-deadline">Deadline (optional)</Label>
<Input
id="task-deadline"
type="date"
value={newDeadline}
onChange={(e) => setNewDeadline(e.target.value)}
/>
</div>
</div>
<DialogFooter className="mt-4">

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<b53893b96884bbc49ad8f2dbdb22e2ea>>
* @generated SignedSource<<d258cc7c047ea5b1e81d5ef895e63ac6>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -12,6 +12,7 @@ import { ConcreteRequest } from 'relay-runtime';
export type TaskState = "DONE" | "TODO";
export type CreateTaskInput = {
assignedToId?: string | null | undefined;
deadline?: string | null | undefined;
description: string;
measureId?: string | null | undefined;
name: string;
@@ -31,6 +32,7 @@ export type MeasureViewCreateTaskMutation$data = {
readonly id: string;
readonly primaryEmailAddress: string;
} | null | undefined;
readonly deadline: string | null | undefined;
readonly description: string;
readonly id: string;
readonly name: string;
@@ -108,6 +110,13 @@ v4 = {
"name": "timeEstimate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deadline",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -213,16 +222,16 @@ return {
]
},
"params": {
"cacheID": "53b934e0e7a3d4abf8789ad8672687df",
"cacheID": "8f9aeb0df92f9535d728ef27a7dee963",
"id": null,
"metadata": {},
"name": "MeasureViewCreateTaskMutation",
"operationKind": "mutation",
"text": "mutation MeasureViewCreateTaskMutation(\n $input: CreateTaskInput!\n) {\n createTask(input: $input) {\n taskEdge {\n node {\n id\n name\n description\n timeEstimate\n state\n assignedTo {\n id\n fullName\n primaryEmailAddress\n }\n }\n }\n }\n}\n"
"text": "mutation MeasureViewCreateTaskMutation(\n $input: CreateTaskInput!\n) {\n createTask(input: $input) {\n taskEdge {\n node {\n id\n name\n description\n timeEstimate\n deadline\n state\n assignedTo {\n id\n fullName\n primaryEmailAddress\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "0df98f5aee4233dc4ffedcff8256891a";
(node as any).hash = "f323b5caa0912478e715f0c409898eff";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<03d8e2eca1cc7ffaf797c66ef7fa11c1>>
* @generated SignedSource<<a6d5ebf3d09595b1da2cd16746fa1163>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -48,6 +48,7 @@ export type MeasureViewQuery$data = {
readonly id: string;
readonly primaryEmailAddress: string;
} | null | undefined;
readonly deadline: string | null | undefined;
readonly description: string;
readonly evidences: {
readonly __id: string;
@@ -261,6 +262,13 @@ v12 = {
"storageKey": null
},
v13 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deadline",
"storageKey": null
},
v14 = {
"alias": null,
"args": null,
"concreteType": "People",
@@ -286,14 +294,14 @@ v13 = {
],
"storageKey": null
},
v14 = [
v15 = [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
v15 = [
v16 = [
{
"kind": "Literal",
"name": "first",
@@ -363,6 +371,7 @@ return {
(v5/*: any*/),
(v12/*: any*/),
(v13/*: any*/),
(v14/*: any*/),
{
"alias": "evidences",
"args": null,
@@ -422,7 +431,7 @@ return {
(v6/*: any*/),
{
"alias": null,
"args": (v14/*: any*/),
"args": (v15/*: any*/),
"concreteType": "EvidenceConnection",
"kind": "LinkedField",
"name": "evidences",
@@ -432,7 +441,7 @@ return {
},
{
"alias": null,
"args": (v14/*: any*/),
"args": (v15/*: any*/),
"filters": null,
"handle": "connection",
"key": "MeasureView_evidences",
@@ -441,7 +450,7 @@ return {
},
{
"alias": null,
"args": (v14/*: any*/),
"args": (v15/*: any*/),
"concreteType": "TaskConnection",
"kind": "LinkedField",
"name": "tasks",
@@ -469,9 +478,10 @@ return {
(v5/*: any*/),
(v12/*: any*/),
(v13/*: any*/),
(v14/*: any*/),
{
"alias": null,
"args": (v15/*: any*/),
"args": (v16/*: any*/),
"concreteType": "EvidenceConnection",
"kind": "LinkedField",
"name": "evidences",
@@ -481,7 +491,7 @@ return {
},
{
"alias": null,
"args": (v15/*: any*/),
"args": (v16/*: any*/),
"filters": null,
"handle": "connection",
"key": "MeasureView_task_evidences",
@@ -503,7 +513,7 @@ return {
},
{
"alias": null,
"args": (v14/*: any*/),
"args": (v15/*: any*/),
"filters": null,
"handle": "connection",
"key": "MeasureView_tasks",
@@ -520,7 +530,7 @@ return {
]
},
"params": {
"cacheID": "d3e4d9723d12bce8158b83eb76d05a90",
"cacheID": "2693fa3e0141f58235566f539194fcd6",
"id": null,
"metadata": {
"connection": [
@@ -552,11 +562,11 @@ return {
},
"name": "MeasureViewQuery",
"operationKind": "query",
"text": "query MeasureViewQuery(\n $measureId: ID!\n) {\n measure: node(id: $measureId) {\n __typename\n id\n ... on Measure {\n name\n description\n state\n category\n evidences(first: 100) {\n edges {\n node {\n id\n mimeType\n filename\n size\n state\n type\n url\n createdAt\n description\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n tasks(first: 100) {\n edges {\n node {\n id\n name\n description\n state\n timeEstimate\n assignedTo {\n id\n fullName\n primaryEmailAddress\n }\n evidences(first: 50) {\n edges {\n node {\n id\n mimeType\n filename\n size\n state\n type\n url\n createdAt\n description\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
"text": "query MeasureViewQuery(\n $measureId: ID!\n) {\n measure: node(id: $measureId) {\n __typename\n id\n ... on Measure {\n name\n description\n state\n category\n evidences(first: 100) {\n edges {\n node {\n id\n mimeType\n filename\n size\n state\n type\n url\n createdAt\n description\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n tasks(first: 100) {\n edges {\n node {\n id\n name\n description\n state\n timeEstimate\n deadline\n assignedTo {\n id\n fullName\n primaryEmailAddress\n }\n evidences(first: 50) {\n edges {\n node {\n id\n mimeType\n filename\n size\n state\n type\n url\n createdAt\n description\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "288412cccb1f4c14b1852504e5536af1";
(node as any).hash = "4e211c36d94ebd914b6de254dfbdf946";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<1595fa9771d6baa14534b181f4a1384a>>
* @generated SignedSource<<28c68c446adeac27a47de2b754d48d5d>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -11,6 +11,7 @@
import { ConcreteRequest } from 'relay-runtime';
export type TaskState = "DONE" | "TODO";
export type UpdateTaskInput = {
deadline?: string | null | undefined;
description?: string | null | undefined;
name?: string | null | undefined;
state?: TaskState | null | undefined;
@@ -23,6 +24,7 @@ export type MeasureViewUpdateTaskStateMutation$variables = {
export type MeasureViewUpdateTaskStateMutation$data = {
readonly updateTask: {
readonly task: {
readonly deadline: string | null | undefined;
readonly id: string;
readonly state: TaskState;
readonly timeEstimate: any | null | undefined;
@@ -85,6 +87,13 @@ v1 = [
"kind": "ScalarField",
"name": "timeEstimate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deadline",
"storageKey": null
}
],
"storageKey": null
@@ -111,16 +120,16 @@ return {
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "f545a0b12edb8f231a1a7a7c38b6e905",
"cacheID": "a0001fad3af2ba3fc16a1f0af4f19370",
"id": null,
"metadata": {},
"name": "MeasureViewUpdateTaskStateMutation",
"operationKind": "mutation",
"text": "mutation MeasureViewUpdateTaskStateMutation(\n $input: UpdateTaskInput!\n) {\n updateTask(input: $input) {\n task {\n id\n state\n timeEstimate\n }\n }\n}\n"
"text": "mutation MeasureViewUpdateTaskStateMutation(\n $input: UpdateTaskInput!\n) {\n updateTask(input: $input) {\n task {\n id\n state\n timeEstimate\n deadline\n }\n }\n}\n"
}
};
})();
(node as any).hash = "e1d67c030523e56d6bcd595c27fcb3dc";
(node as any).hash = "b3b0d47e0632b106b6313edbd2de38ce";
export default node;

View File

@@ -1,4 +1,4 @@
import { Suspense, useEffect, useState } from "react";
import {ChangeEvent, Suspense, useEffect, useState} from "react";
import {
useQueryLoader,
graphql,
@@ -47,6 +47,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ListTaskViewOrganizationQuery } from "./__generated__/ListTaskViewOrganizationQuery.graphql";
import {formatDate, parseISO} from "date-fns";
// Function to format ISO8601 duration to human-readable format
const formatDuration = (isoDuration: string | null | undefined): string => {
@@ -121,6 +122,7 @@ const createTaskMutation = graphql`
description
state
timeEstimate
deadline
measure {
id
name
@@ -164,6 +166,7 @@ const listTaskViewQuery = graphql`
description
state
timeEstimate
deadline
measure {
id
name
@@ -213,6 +216,7 @@ interface Task {
description?: string;
state: string;
timeEstimate?: number | null;
deadline?: string | null;
assignedTo?: {
id: string;
fullName: string;
@@ -273,6 +277,11 @@ function TaskCard({ task, onClick, onToggleState }: {
{formatDuration(task.timeEstimate.toString())}
</span>
)}
{task.deadline && (
<span className="text-xs text-gray-500">
{formatDate(task.deadline, "dd MMM yyyy")}
</span>
)}
</div>
{task.parent && (
<div className="flex items-center gap-1 text-sm text-gray-500">
@@ -346,6 +355,8 @@ function ListTaskContent({
const [selectedMeasure, setSelectedMeasure] = useState<{id: string, name: string} | null>(null);
const [timeEstimate, setTimeEstimate] = useState<number | null>(null);
const [timeUnit, setTimeUnit] = useState<"minutes" | "hours" | "days" | "weeks">("hours");
const [deadlineString, setDeadlineString] = useState<string>('');
const [deadline, setDeadline] = useState<Date | null>(null);
// Create task mutation
const [createTask, isCreatingTask] = useMutation(createTaskMutation);
@@ -382,6 +393,19 @@ function ListTaskContent({
}
}, [environment, organization?.id]);
// Parse deadline string into Date-object
const handleDeadlineChange = (event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setDeadlineString(value);
if (value) {
const date = parseISO(value);
setDeadline(date);
} else {
setDeadline(null)
}
};
// Filter measures based on search query
const filteredMeasures = measures.filter(measure =>
measure.name.toLowerCase().includes(measureSearchQuery.toLowerCase()) ||
@@ -439,6 +463,7 @@ function ListTaskContent({
measureId: selectedMeasure?.id || null,
assignedToId: assignee?.id,
timeEstimate: formattedTimeEstimate,
deadline: deadline
},
connections: [data.node?.tasks?.__id || ""],
},
@@ -462,6 +487,8 @@ function ListTaskContent({
setTimeEstimate(null);
setTimeUnit("hours");
setMeasureSearchQuery("");
setDeadlineString('');
setDeadline(null);
};
// Get initials from name
@@ -825,6 +852,15 @@ function ListTaskContent({
)}
</div>
</div>
{/* Deadline */}
<div className="flex justify-between items-center broder-b border-[rgba(2,42,2,0.08)] py-3">
<Label className="text-sm font-medium text-gray-500">Deadline</Label>
<div className="flex items-center gap-1.5">
<Input type="date" value={deadlineString} onChange={handleDeadlineChange}
className="text-sm font-medium bg-[rgba(0,39,0,0.05)] px-[10px] py-[6px] rounded-lg"/>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<84a35602a155b0f1fa8504cf32c10932>>
* @generated SignedSource<<a4c4d53df4bc73d2bdc48ef0eb38a500>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -12,6 +12,7 @@ import { ConcreteRequest } from 'relay-runtime';
export type TaskState = "DONE" | "TODO";
export type CreateTaskInput = {
assignedToId?: string | null | undefined;
deadline?: string | null | undefined;
description: string;
measureId?: string | null | undefined;
name: string;
@@ -30,6 +31,7 @@ export type ListTaskViewCreateTaskMutation$data = {
readonly fullName: string;
readonly id: string;
} | null | undefined;
readonly deadline: string | null | undefined;
readonly description: string;
readonly id: string;
readonly measure: {
@@ -119,6 +121,13 @@ v5 = {
"name": "timeEstimate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deadline",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -223,16 +232,16 @@ return {
]
},
"params": {
"cacheID": "7b9680f637411402ff51239a43b44a14",
"cacheID": "6fe245ddb913332138beda8377ebd88c",
"id": null,
"metadata": {},
"name": "ListTaskViewCreateTaskMutation",
"operationKind": "mutation",
"text": "mutation ListTaskViewCreateTaskMutation(\n $input: CreateTaskInput!\n) {\n createTask(input: $input) {\n taskEdge {\n node {\n id\n name\n description\n state\n timeEstimate\n measure {\n id\n name\n }\n assignedTo {\n id\n fullName\n }\n }\n }\n }\n}\n"
"text": "mutation ListTaskViewCreateTaskMutation(\n $input: CreateTaskInput!\n) {\n createTask(input: $input) {\n taskEdge {\n node {\n id\n name\n description\n state\n timeEstimate\n deadline\n measure {\n id\n name\n }\n assignedTo {\n id\n fullName\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "43ccaf49d31ec84dcf3b6b3f2e3a851c";
(node as any).hash = "934ee4a5b5bfd4c9e9c4acab49d11304";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<7732676a00f7ed0f6817555b68540aca>>
* @generated SignedSource<<14a914bdd269eb18124d002d2c61a5b9>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -25,6 +25,7 @@ export type ListTaskViewQuery$data = {
readonly fullName: string;
readonly id: string;
} | null | undefined;
readonly deadline: string | null | undefined;
readonly description: string;
readonly id: string;
readonly measure: {
@@ -120,6 +121,13 @@ v5 = [
"name": "timeEstimate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deadline",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -301,7 +309,7 @@ return {
]
},
"params": {
"cacheID": "52b577b08c305249855293949d6e2339",
"cacheID": "7519a481d7930f14c53c8c1a72d66f58",
"id": null,
"metadata": {
"connection": [
@@ -318,11 +326,11 @@ return {
},
"name": "ListTaskViewQuery",
"operationKind": "query",
"text": "query ListTaskViewQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n name\n tasks(first: 100) {\n edges {\n node {\n id\n name\n description\n state\n timeEstimate\n measure {\n id\n name\n }\n assignedTo {\n id\n fullName\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
"text": "query ListTaskViewQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n name\n tasks(first: 100) {\n edges {\n node {\n id\n name\n description\n state\n timeEstimate\n deadline\n measure {\n id\n name\n }\n assignedTo {\n id\n fullName\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "5ca091444c547744068f2b2a83cb31df";
(node as any).hash = "52b99d58a5b95dadd4e66bfad588d924";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<7098e326d01cc6831f293726096a7275>>
* @generated SignedSource<<c0e5d40aeab42e5f50cf93b75ddb7328>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -11,6 +11,7 @@
import { ConcreteRequest } from 'relay-runtime';
export type TaskState = "DONE" | "TODO";
export type UpdateTaskInput = {
deadline?: string | null | undefined;
description?: string | null | undefined;
name?: string | null | undefined;
state?: TaskState | null | undefined;

View File

@@ -0,0 +1 @@
ALTER TABLE tasks ADD COLUMN deadline TIMESTAMP WITH TIME ZONE;

View File

@@ -38,6 +38,7 @@ type (
ReferenceID string `db:"reference_id"`
TimeEstimate *time.Duration `db:"time_estimate"`
AssignedToID *gid.GID `db:"assigned_to"`
Deadline *time.Time `db:"deadline"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
@@ -71,6 +72,7 @@ SELECT
reference_id,
time_estimate,
assigned_to,
deadline,
created_at,
updated_at
FROM
@@ -119,6 +121,7 @@ INSERT INTO
state,
time_estimate,
assigned_to,
deadline,
created_at,
updated_at
)
@@ -133,6 +136,7 @@ VALUES (
@state,
@time_estimate,
@assigned_to,
@deadline,
@created_at,
@updated_at
);
@@ -149,6 +153,7 @@ VALUES (
"state": c.State,
"time_estimate": c.TimeEstimate,
"assigned_to": c.AssignedToID,
"deadline": c.Deadline,
"created_at": c.CreatedAt,
"updated_at": c.UpdatedAt,
}
@@ -174,6 +179,7 @@ INSERT INTO
state,
time_estimate,
assigned_to,
deadline,
created_at,
updated_at
)
@@ -188,6 +194,7 @@ VALUES (
@state,
@time_estimate,
@assigned_to,
@deadline,
@created_at,
@updated_at
)
@@ -205,6 +212,7 @@ RETURNING
state,
time_estimate,
assigned_to,
deadline,
created_at,
updated_at
`
@@ -220,6 +228,7 @@ RETURNING
"state": c.State,
"time_estimate": c.TimeEstimate,
"assigned_to": c.AssignedToID,
"deadline": c.Deadline,
"created_at": c.CreatedAt,
"updated_at": c.UpdatedAt,
}
@@ -256,6 +265,7 @@ func (c *Tasks) LoadByOrganizationID(
reference_id,
time_estimate,
assigned_to,
deadline,
created_at,
updated_at
FROM
@@ -304,6 +314,7 @@ SELECT
reference_id,
time_estimate,
assigned_to,
deadline,
created_at,
updated_at
FROM
@@ -347,7 +358,8 @@ SET
state = @state,
time_estimate = @time_estimate,
updated_at = @updated_at,
assigned_to = @assigned_to
assigned_to = @assigned_to,
deadline = @deadline
WHERE %s
AND id = @task_id
`
@@ -361,6 +373,7 @@ WHERE %s
"time_estimate": c.TimeEstimate,
"updated_at": c.UpdatedAt,
"assigned_to": c.AssignedToID,
"deadline": c.Deadline,
}
maps.Copy(args, scope.SQLArguments())

View File

@@ -38,6 +38,7 @@ type (
Description string
TimeEstimate *time.Duration
AssignedToID *gid.GID
Deadline *time.Time
}
UpdateTaskRequest struct {
@@ -46,6 +47,7 @@ type (
Description *string
State *coredata.TaskState
TimeEstimate *time.Duration
Deadline *time.Time
}
)
@@ -69,6 +71,7 @@ func (s TaskService) Create(
Description: req.Description,
TimeEstimate: req.TimeEstimate,
AssignedToID: req.AssignedToID,
Deadline: req.Deadline,
State: coredata.TaskStateTodo,
ReferenceID: "custom-task-" + referenceID.String(),
CreatedAt: now,
@@ -203,6 +206,10 @@ func (s TaskService) Update(
task.TimeEstimate = req.TimeEstimate
}
if req.Deadline != nil {
task.Deadline = req.Deadline
}
task.UpdatedAt = time.Now()
if err := task.Update(ctx, conn, s.svc.scope); err != nil {

View File

@@ -758,6 +758,7 @@ type Task implements Node {
description: String!
state: TaskState!
timeEstimate: Duration
deadline: Datetime
assignedTo: People @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true)
@@ -1329,6 +1330,7 @@ input CreateTaskInput {
description: String!
timeEstimate: Duration
assignedToId: ID
deadline: Datetime
}
input UpdateTaskInput {
@@ -1337,6 +1339,7 @@ input UpdateTaskInput {
description: String
state: TaskState
timeEstimate: Duration
deadline: Datetime
}
input DeleteTaskInput {

View File

@@ -680,6 +680,7 @@ type ComplexityRoot struct {
Task struct {
AssignedTo func(childComplexity int) int
CreatedAt func(childComplexity int) int
Deadline func(childComplexity int) int
Description func(childComplexity int) int
Evidences func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) int
ID func(childComplexity int) int
@@ -3834,6 +3835,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Task.CreatedAt(childComplexity), true
case "Task.deadline":
if e.complexity.Task.Deadline == nil {
break
}
return e.complexity.Task.Deadline(childComplexity), true
case "Task.description":
if e.complexity.Task.Description == nil {
break
@@ -5481,6 +5489,7 @@ type Task implements Node {
description: String!
state: TaskState!
timeEstimate: Duration
deadline: Datetime
assignedTo: People @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true)
@@ -6052,6 +6061,7 @@ input CreateTaskInput {
description: String!
timeEstimate: Duration
assignedToId: ID
deadline: Datetime
}
input UpdateTaskInput {
@@ -6060,6 +6070,7 @@ input UpdateTaskInput {
description: String
state: TaskState
timeEstimate: Duration
deadline: Datetime
}
input DeleteTaskInput {
@@ -12379,6 +12390,8 @@ func (ec *executionContext) fieldContext_AssignTaskPayload_task(_ context.Contex
return ec.fieldContext_Task_state(ctx, field)
case "timeEstimate":
return ec.fieldContext_Task_timeEstimate(ctx, field)
case "deadline":
return ec.fieldContext_Task_deadline(ctx, field)
case "assignedTo":
return ec.fieldContext_Task_assignedTo(ctx, field)
case "organization":
@@ -18910,6 +18923,8 @@ func (ec *executionContext) fieldContext_Evidence_task(_ context.Context, field
return ec.fieldContext_Task_state(ctx, field)
case "timeEstimate":
return ec.fieldContext_Task_timeEstimate(ctx, field)
case "deadline":
return ec.fieldContext_Task_deadline(ctx, field)
case "assignedTo":
return ec.fieldContext_Task_assignedTo(ctx, field)
case "organization":
@@ -28879,6 +28894,47 @@ func (ec *executionContext) fieldContext_Task_timeEstimate(_ context.Context, fi
return fc, nil
}
func (ec *executionContext) _Task_deadline(ctx context.Context, field graphql.CollectedField, obj *types.Task) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Task_deadline(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return obj.Deadline, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*time.Time)
fc.Result = res
return ec.marshalODatetime2ᚖtimeᚐTime(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Task_deadline(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Task",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Datetime does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Task_assignedTo(ctx context.Context, field graphql.CollectedField, obj *types.Task) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Task_assignedTo(ctx, field)
if err != nil {
@@ -29431,6 +29487,8 @@ func (ec *executionContext) fieldContext_TaskEdge_node(_ context.Context, field
return ec.fieldContext_Task_state(ctx, field)
case "timeEstimate":
return ec.fieldContext_Task_timeEstimate(ctx, field)
case "deadline":
return ec.fieldContext_Task_deadline(ctx, field)
case "assignedTo":
return ec.fieldContext_Task_assignedTo(ctx, field)
case "organization":
@@ -29499,6 +29557,8 @@ func (ec *executionContext) fieldContext_UnassignTaskPayload_task(_ context.Cont
return ec.fieldContext_Task_state(ctx, field)
case "timeEstimate":
return ec.fieldContext_Task_timeEstimate(ctx, field)
case "deadline":
return ec.fieldContext_Task_deadline(ctx, field)
case "assignedTo":
return ec.fieldContext_Task_assignedTo(ctx, field)
case "organization":
@@ -30193,6 +30253,8 @@ func (ec *executionContext) fieldContext_UpdateTaskPayload_task(_ context.Contex
return ec.fieldContext_Task_state(ctx, field)
case "timeEstimate":
return ec.fieldContext_Task_timeEstimate(ctx, field)
case "deadline":
return ec.fieldContext_Task_deadline(ctx, field)
case "assignedTo":
return ec.fieldContext_Task_assignedTo(ctx, field)
case "organization":
@@ -36789,7 +36851,7 @@ func (ec *executionContext) unmarshalInputCreateTaskInput(ctx context.Context, o
asMap[k] = v
}
fieldsInOrder := [...]string{"organizationId", "measureId", "name", "description", "timeEstimate", "assignedToId"}
fieldsInOrder := [...]string{"organizationId", "measureId", "name", "description", "timeEstimate", "assignedToId", "deadline"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
@@ -36838,6 +36900,13 @@ func (ec *executionContext) unmarshalInputCreateTaskInput(ctx context.Context, o
return it, err
}
it.AssignedToID = data
case "deadline":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("deadline"))
data, err := ec.unmarshalODatetime2ᚖtimeᚐTime(ctx, v)
if err != nil {
return it, err
}
it.Deadline = data
}
}
@@ -38947,7 +39016,7 @@ func (ec *executionContext) unmarshalInputUpdateTaskInput(ctx context.Context, o
asMap[k] = v
}
fieldsInOrder := [...]string{"taskId", "name", "description", "state", "timeEstimate"}
fieldsInOrder := [...]string{"taskId", "name", "description", "state", "timeEstimate", "deadline"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
@@ -38989,6 +39058,13 @@ func (ec *executionContext) unmarshalInputUpdateTaskInput(ctx context.Context, o
return it, err
}
it.TimeEstimate = data
case "deadline":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("deadline"))
data, err := ec.unmarshalODatetime2ᚖtimeᚐTime(ctx, v)
if err != nil {
return it, err
}
it.Deadline = data
}
}
@@ -46022,6 +46098,8 @@ func (ec *executionContext) _Task(ctx context.Context, sel ast.SelectionSet, obj
}
case "timeEstimate":
out.Values[i] = ec._Task_timeEstimate(ctx, field, obj)
case "deadline":
out.Values[i] = ec._Task_deadline(ctx, field, obj)
case "assignedTo":
field := field

View File

@@ -52,5 +52,6 @@ func NewTask(t *coredata.Task) *Task {
TimeEstimate: t.TimeEstimate,
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
Deadline: t.Deadline,
}
}

View File

@@ -313,6 +313,7 @@ type CreateTaskInput struct {
Description string `json:"description"`
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
AssignedToID *gid.GID `json:"assignedToId,omitempty"`
Deadline *time.Time `json:"deadline,omitempty"`
}
type CreateTaskPayload struct {
@@ -934,6 +935,7 @@ type Task struct {
Description string `json:"description"`
State coredata.TaskState `json:"state"`
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
Deadline *time.Time `json:"deadline,omitempty"`
AssignedTo *People `json:"assignedTo,omitempty"`
Organization *Organization `json:"organization"`
Measure *Measure `json:"measure,omitempty"`
@@ -1083,6 +1085,7 @@ type UpdateTaskInput struct {
Description *string `json:"description,omitempty"`
State *coredata.TaskState `json:"state,omitempty"`
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
Deadline *time.Time `json:"deadline,omitempty"`
}
type UpdateTaskPayload struct {

View File

@@ -1114,6 +1114,7 @@ func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTas
Name: input.Name,
Description: input.Description,
TimeEstimate: input.TimeEstimate,
Deadline: input.Deadline,
})
if err != nil {
panic(fmt.Errorf("cannot create task: %w", err))
@@ -1134,6 +1135,7 @@ func (r *mutationResolver) UpdateTask(ctx context.Context, input types.UpdateTas
Description: input.Description,
State: input.State,
TimeEstimate: input.TimeEstimate,
Deadline: input.Deadline,
})
if err != nil {
panic(fmt.Errorf("cannot update task: %w", err))