Skip to content

Commit

Permalink
[toggl-track] Add ability to create tasks for time entry form (raycas…
Browse files Browse the repository at this point in the history
…t#14737)

* ✨ Add ability to create task for time entry form

* 📝 Update changelog

* 🚸 Clear task search on blur and task creation

* Update CHANGELOG.md

* Update CHANGELOG.md and optimise images

---------

Co-authored-by: Per Nielsen Tikær <[email protected]>
Co-authored-by: raycastbot <[email protected]>
  • Loading branch information
3 people authored Oct 7, 2024
1 parent 4a1fca5 commit 97861a8
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 14 deletions.
4 changes: 4 additions & 0 deletions extensions/toggl-track/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Toggl Track Changelog

## [New Feature] - 2024-10-07

- Add ability to create task for time entry form

## [Bug Fixes] - 2024-08-20

- Remove seconds from optional timer in the Menu Bar, as it only updates every 10 seconds.
Expand Down
2 changes: 1 addition & 1 deletion extensions/toggl-track/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export {
type Client,
} from "@/api/clients";
export { getMyTags, createTag, updateTag, deleteTag, type Tag } from "@/api/tags";
export { getMyTasks, type Task } from "@/api/tasks";
export { getMyTasks, createTask, type Task } from "@/api/tasks";
export {
getMyTimeEntries,
createTimeEntry,
Expand Down
6 changes: 5 additions & 1 deletion extensions/toggl-track/src/api/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { get } from "@/api/togglClient";
import { get, post } from "@/api/togglClient";
import type { ToggleItem } from "@/api/types";

export function getMyTasks() {
return get<Task[]>("/me/tasks");
}

export function createTask(workspaceId: number, projectId: number, name: string) {
return post<Task>(`/workspaces/${workspaceId}/projects/${projectId}/tasks`, { name });
}

/** @see {@link https://developers.track.toggl.com/docs/api/tasks#response Toggl Api} */
export interface Task extends ToggleItem {
active: boolean;
Expand Down
66 changes: 54 additions & 12 deletions extensions/toggl-track/src/components/CreateTimeEntryForm.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { Action, ActionPanel, Form, Icon, Toast, clearSearchBar, showToast, useNavigation } from "@raycast/api";
import { useCachedState } from "@raycast/utils";
import {
Action,
ActionPanel,
Form,
Icon,
Toast,
clearSearchBar,
showToast,
useNavigation,
confirmAlert,
} from "@raycast/api";
import { useCachedState, showFailureToast } from "@raycast/utils";
import { useMemo, useState } from "react";

import { Client, Project, Task, TimeEntry, TimeEntryMetaData, createTimeEntry } from "@/api";
import { Client, Project, Task, TimeEntry, TimeEntryMetaData, createTimeEntry, createTask } from "@/api";
import { useClients, useMe, useProjects, useTags, useTasks, useWorkspaces } from "@/hooks";

interface CreateTimeEntryFormParams {
Expand All @@ -21,7 +31,7 @@ function CreateTimeEntryForm({
const { workspaces, isLoadingWorkspaces } = useWorkspaces();
const { clients, isLoadingClients } = useClients();
const { projects, isLoadingProjects } = useProjects();
const { tasks, isLoadingTasks } = useTasks();
const { tasks, isLoadingTasks, revalidateTasks } = useTasks();
const { tags, isLoadingTags } = useTags();

const [selectedWorkspace, setSelectedWorkspace] = useCachedState("defaultWorkspace", workspaces.at(0)?.id);
Expand All @@ -37,6 +47,8 @@ function CreateTimeEntryForm({
const [selectedTags, setSelectedTags] = useState<string[]>(initialValues?.tags || []);
const [billable, setBillable] = useState(initialValues?.billable || false);

const [taskSearch, setTaskSearch] = useState("");

async function handleSubmit(values: { description: string; billable?: boolean }) {
const workspaceId = selectedProject?.workspace_id || me?.default_workspace_id;

Expand Down Expand Up @@ -110,9 +122,31 @@ function CreateTimeEntryForm({
if (project) setSelectedProject(project);
};

const onTaskChange = (taskId: string) => {
const task = tasks.find((task) => task.id === parseInt(taskId));
setSelectedTask(task);
const onTaskChange = async (taskId: string) => {
if (taskId == "new_task") {
const newTaskName = taskSearch;
setTaskSearch("");
if (await confirmAlert({ title: "Create new task?", message: "Task name: " + newTaskName })) {
const toast = await showToast(Toast.Style.Animated, "Creating task...");
try {
if (!selectedWorkspace) throw Error("Workspace ID is undefined.");
if (!selectedProject) throw Error("Workspace ID is undefined.");
const newTask = await createTask(selectedWorkspace, selectedProject.id, newTaskName);
revalidateTasks();
setSelectedTask(newTask);
toast.style = Toast.Style.Success;
toast.title = "Created task";
} catch (error) {
await toast.hide();
await showFailureToast(error);
}
} else {
setSelectedTask(undefined);
}
} else {
const task = tasks.find((task) => task.id === parseInt(taskId));
setSelectedTask(task);
}
};

return (
Expand Down Expand Up @@ -167,7 +201,7 @@ function CreateTimeEntryForm({
>
{!isLoadingProjects && (
<>
<Form.Dropdown.Item key="-1" value="-1" title={"No Project"} icon={{ source: Icon.Circle }} />
<Form.Dropdown.Item key="-1" value="-1" title={"No Project"} icon={Icon.Circle} />
{filteredProjects.map((project) => (
<Form.Dropdown.Item
key={project.id}
Expand All @@ -180,14 +214,22 @@ function CreateTimeEntryForm({
)}
</Form.Dropdown>

{selectedProject && filteredTasks.length > 0 && (
<Form.Dropdown id="task" title="Task" defaultValue={"-1"} onChange={onTaskChange}>
{selectedProject && (
<Form.Dropdown
id="task"
title="Task"
onChange={onTaskChange}
value={selectedTask?.id.toString() ?? "-1"}
onSearchTextChange={setTaskSearch}
onBlur={() => setTaskSearch("")}
>
{!isLoadingTasks && (
<>
<Form.Dropdown.Item value="-1" title={"No task"} icon={{ source: Icon.Circle }} />
<Form.Dropdown.Item value="-1" title={"No task"} icon={Icon.Circle} />
{filteredTasks.map((task) => (
<Form.Dropdown.Item key={task.id} value={task.id.toString()} title={task.name} />
<Form.Dropdown.Item key={task.id} value={task.id.toString()} title={task.name} icon={Icon.Circle} />
))}
{taskSearch !== "" && <Form.Dropdown.Item value="new_task" title={taskSearch} icon={Icon.PlusCircle} />}
</>
)}
</Form.Dropdown>
Expand Down

0 comments on commit 97861a8

Please sign in to comment.