Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Toggl-track] Update toggl-track extension #15600

Merged
merged 3 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions extensions/toggl-track/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Toggl Track Changelog

## [New Feature] - 2024-12-09

- Add "Update Time Entry" command to update time entries
- Add ability to update time entry action from the list

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

- Add ability to create task for time entry form
Expand Down
11 changes: 9 additions & 2 deletions extensions/toggl-track/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"bkeys818",
"michaelfaisst",
"teziovsky",
"lukebars"
"lukebars",
"AlanHuang"
],
"license": "MIT",
"commands": [
Expand All @@ -25,6 +26,12 @@
"description": "Start a new time entry or end the running time entry.",
"mode": "view"
},
{
"name": "updateTimeEntry",
"title": "Update Time Entry",
"description": "Update the description, project, tags of recent time entries.",
"mode": "view"
},
{
"name": "manageWorkspaces",
"title": "View Workspaces",
Expand Down Expand Up @@ -129,4 +136,4 @@
"publish": "npx @raycast/api@latest publish",
"pull-contributions": "npx @raycast/api@latest pull-contributions"
}
}
}
3 changes: 3 additions & 0 deletions extensions/toggl-track/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export {
createTimeEntry,
stopTimeEntry,
getRunningTimeEntry,
updateTimeEntry,
removeTimeEntry,
type TimeEntry,
type TimeEntryMetaData,
type UpdateTimeEntryParams,
} from "@/api/timeEntries";
21 changes: 20 additions & 1 deletion extensions/toggl-track/src/api/timeEntries.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { get, post, patch, remove } from "@/api/togglClient";
import { get, post, patch, put, remove } from "@/api/togglClient";
import type { ToggleItem } from "@/api/types";

export async function getMyTimeEntries<Meta extends boolean = false>({
Expand Down Expand Up @@ -59,6 +59,25 @@ export function removeTimeEntry(workspaceId: number, timeEntryId: number) {
return remove(`/workspaces/${workspaceId}/time_entries/${timeEntryId}`);
}

export interface UpdateTimeEntryParams {
billable?: boolean;
created_with?: string;
description?: string;
duration?: number;
duronly?: boolean;
project_id?: number;
start?: string;
stop?: string;
tag_ids?: number[];
tags?: string[];
task_id?: number;
workspace_id?: number;
}

export function updateTimeEntry(workspaceId: number, timeEntryId: number, params: UpdateTimeEntryParams) {
return put<TimeEntry>(`/workspaces/${workspaceId}/time_entries/${timeEntryId}`, params);
}

// https://developers.track.toggl.com/docs/api/time_entries#response
export interface TimeEntry extends ToggleItem {
billable: boolean;
Expand Down
46 changes: 46 additions & 0 deletions extensions/toggl-track/src/components/TimeEntriesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ActionPanel, Icon, List } from "@raycast/api";

import { TimeEntry, TimeEntryMetaData } from "@/api";
import { formatSeconds } from "@/helpers/formatSeconds";

interface TimeEntriesListProps {
isLoading: boolean;
timeEntries: (TimeEntry & TimeEntryMetaData)[];
navigationTitle?: string;
sectionTitle?: string;
renderActions: (timeEntry: TimeEntry & TimeEntryMetaData) => JSX.Element;
}

export function TimeEntriesList({
isLoading,
timeEntries,
navigationTitle,
sectionTitle = "Recent time entries",
renderActions,
}: TimeEntriesListProps) {
return (
<List isLoading={isLoading} throttle navigationTitle={navigationTitle}>
{timeEntries.length > 0 && (
<List.Section title={sectionTitle}>
{timeEntries.map((timeEntry) => (
<List.Item
key={timeEntry.id}
keywords={[timeEntry.description, timeEntry.project_name || "", timeEntry.client_name || ""]}
title={timeEntry.description || "No description"}
subtitle={(timeEntry.client_name ? timeEntry.client_name + " | " : "") + (timeEntry.project_name ?? "")}
accessories={[
{ text: formatSeconds(timeEntry.duration) },
...timeEntry.tags.map((tag) => ({ tag })),
timeEntry.billable ? { tag: { value: "$" } } : {},
]}
icon={{ source: Icon.Circle, tintColor: timeEntry.project_color }}
actions={<ActionPanel>{renderActions(timeEntry)}</ActionPanel>}
/>
))}
</List.Section>
)}
</List>
);
}

export default TimeEntriesList;
92 changes: 92 additions & 0 deletions extensions/toggl-track/src/components/TimeEntriesListView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Action, ActionPanel, Alert, Color, Icon, List, confirmAlert } from "@raycast/api";

import { removeTimeEntry } from "@/api";
import { ExtensionContextProvider } from "@/context/ExtensionContext";
import { formatSeconds } from "@/helpers/formatSeconds";
import { Verb, withToast } from "@/helpers/withToast";
import { useProcessedTimeEntries } from "@/hooks/useProcessedTimeEntries";
import { useTotalDurationToday } from "@/hooks/useTotalDurationToday";

import UpdateTimeEntryForm from "./UpdateTimeEntryForm";

export function TimeEntriesListView() {
const {
isLoading,
mutateTimeEntries,
timeEntries,
timeEntriesWithUniqueProjectAndDescription,
revalidateTimeEntries,
} = useProcessedTimeEntries();

const totalDurationToday = useTotalDurationToday(timeEntries);

return (
<List
isLoading={isLoading}
throttle
navigationTitle={isLoading ? undefined : `Total: ${formatSeconds(totalDurationToday)}`}
>
{timeEntriesWithUniqueProjectAndDescription.length > 0 && (
<List.Section title="Recent time entries">
{timeEntriesWithUniqueProjectAndDescription.map((timeEntry) => (
<List.Item
key={timeEntry.id}
keywords={[timeEntry.description, timeEntry.project_name || "", timeEntry.client_name || ""]}
title={timeEntry.description || "No description"}
subtitle={(timeEntry.client_name ? timeEntry.client_name + " | " : "") + (timeEntry.project_name ?? "")}
accessories={[
{ text: formatSeconds(timeEntry.duration) },
...timeEntry.tags.map((tag) => ({ tag })),
timeEntry.billable ? { tag: { value: "$" } } : {},
]}
icon={{ source: Icon.Circle, tintColor: timeEntry.project_color }}
actions={
<ActionPanel>
<Action.Push
title="Edit Time Entry"
icon={Icon.Pencil}
target={
<ExtensionContextProvider>
<UpdateTimeEntryForm timeEntry={timeEntry} revalidateTimeEntries={revalidateTimeEntries} />
</ExtensionContextProvider>
}
/>
<ActionPanel.Section>
<Action
title="Delete Time Entry"
icon={Icon.Trash}
style={Action.Style.Destructive}
shortcut={{ modifiers: ["ctrl"], key: "x" }}
onAction={async () => {
await confirmAlert({
title: "Delete Time Entry",
message: "Are you sure you want to delete this time entry?",
icon: { source: Icon.Trash, tintColor: Color.Red },
primaryAction: {
title: "Delete",
style: Alert.ActionStyle.Destructive,
onAction: () => {
withToast({
noun: "Time Entry",
verb: Verb.Delete,
action: async () => {
await mutateTimeEntries(removeTimeEntry(timeEntry.workspace_id, timeEntry.id));
},
});
},
},
});
}}
/>
</ActionPanel.Section>
</ActionPanel>
}
/>
))}
</List.Section>
)}
</List>
);
}

export default TimeEntriesListView;
Loading