Skip to content

Commit

Permalink
[Toggl-track] Update toggl-track extension (#15600)
Browse files Browse the repository at this point in the history
* Update toggl-track extension

- Fix lint and update changelog
- Add UpdateTimeEntry command and update and remove time entry functionality
- Initial commit

* 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 Dec 9, 2024
1 parent 324853f commit e2d083c
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 3 deletions.
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

0 comments on commit e2d083c

Please sign in to comment.