diff --git a/src/widgets/todoist/agenda.jsx b/src/widgets/todoist/agenda.jsx index cb59976fe..f0a82a3a9 100644 --- a/src/widgets/todoist/agenda.jsx +++ b/src/widgets/todoist/agenda.jsx @@ -3,30 +3,27 @@ import classNames from "classnames"; import Event from "./event"; const colorVariants = { - // https://tailwindcss.com/docs/content-configuration#dynamic-class-names - amber: "bg-amber-500", - blue: "bg-blue-500", - cyan: "bg-cyan-500", - emerald: "bg-emerald-500", - fuchsia: "bg-fuchsia-500", - gray: "bg-gray-500", - green: "bg-green-500", - indigo: "bg-indigo-500", - lime: "bg-lime-500", - neutral: "bg-neutral-500", - orange: "bg-orange-500", - pink: "bg-pink-500", - purple: "bg-purple-500", - red: "bg-red-500", - rose: "bg-rose-500", - sky: "bg-sky-500", - slate: "bg-slate-500", - stone: "bg-stone-500", - teal: "bg-teal-500", - violet: "bg-violet-500", - white: "bg-white-500", - yellow: "bg-yellow-500", - zinc: "bg-zinc-500", + // Custom colors for Todoist + "berry_red": "bg-pink-500", + "red": "bg-red-500", + "orange": "bg-orange-500", + "yellow": "bg-yellow-500", + "olive_green": "bg-green-500", + "lime_green": "bg-lime-500", + "green": "bg-green-500", + "mint_green": "bg-green-400", + "teal": "bg-teal-500", + "sky_blue": "bg-blue-300", + "light_blue": "bg-blue-200", + "blue": "bg-blue-500", + "grape": "bg-purple-500", + "violet": "bg-purple-700", + "lavender": "bg-purple-300", + "magenta": "bg-pink-500", + "salmon": "bg-red-300", + "charcoal": "bg-gray-700", + "grey": "bg-gray-500", + "taupe": "bg-gray-400", }; export default function Agenda({ tasks }) { diff --git a/src/widgets/todoist/categories/filter.jsx b/src/widgets/todoist/categories/filter.jsx index e69de29bb..ec18f6ca5 100644 --- a/src/widgets/todoist/categories/filter.jsx +++ b/src/widgets/todoist/categories/filter.jsx @@ -0,0 +1,42 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "next-i18next"; +import { DateTime } from "luxon"; // Import Luxon for timezone conversion + +import useWidgetAPI from "../../../utils/proxy/use-widget-api"; +import Error from "../../../components/services/widget/error"; +import Agenda from "../agenda"; + +export default function Filter({ widget }) { + const { t } = useTranslation(); + const { data: tasksData, error: tasksError } = useWidgetAPI(widget, "getTasksWithCustomFilter", { + refreshInterval: widget.refreshInterval || 300000, // 5 minutes, use default if not provided + filter: widget.filter + }); + + const [tasks, setTasks] = useState([]); // State to hold tasks + + useEffect(() => { + if (!tasksError && tasksData && tasksData.length > 0) { + // Process label data and set tasks + const tasksToAdd = tasksData.slice(0, widget.maxTasks || tasksData.length).map((task) => ({ + title: task.content || t("Untitled Task by Label"), + date: task.due ? DateTime.fromISO(task.due.date, { zone: widget.timeZone }).toJSDate() : null, + color: widget.color || task.color || "blue", // Adjust color based on your preference + description: task.tags ? task.tags.join(", ") : "", + url: task.url, + id: task.id, + })); + + // Update the tasks state + setTasks(tasksToAdd); + } + }, [tasksData, tasksError, widget, t, setTasks]); + + const error = tasksError ?? tasksData?.error; + if (error && !widget.hideErrors) { + return ; + } + + // Render the Agenda component if tasks is not empty + return ; +} diff --git a/src/widgets/todoist/categories/label.jsx b/src/widgets/todoist/categories/label.jsx index e2a4947ef..bffe9dd2a 100644 --- a/src/widgets/todoist/categories/label.jsx +++ b/src/widgets/todoist/categories/label.jsx @@ -4,12 +4,13 @@ import { useTranslation } from "next-i18next"; import useWidgetAPI from "../../../utils/proxy/use-widget-api"; import Error from "../../../components/services/widget/error"; import Agenda from "../agenda"; +import { DateTime } from "luxon"; export default function Label({ widget }) { const { t } = useTranslation(); const { data: tasksData, error: tasksError } = useWidgetAPI(widget, "getTasksWithLabel", { refreshInterval: widget.refreshInterval || 300000, // 5 minutes, use default if not provided - label: widget.name + label: widget.label }); const [tasks, setEvents] = useState([]); // State to hold events @@ -19,8 +20,8 @@ export default function Label({ widget }) { // Process label data and set tasks const tasksToAdd = tasksData.slice(0, widget.maxTasks || tasksData.length).map((task) => ({ title: task.content || t("Untitled Task by Label"), - date: task.due ? new Date(task.due.date) : null, - color: task.color || "blue", // Adjust color based on your preference + date: task.due ? DateTime.fromISO(task.due.date, { zone: widget.timeZone }).toJSDate() : null, + color: widget.color || task.color || "blue", // Adjust color based on your preference description: task.tags ? task.tags.join(", ") : "", url: task.url, id: task.id, diff --git a/src/widgets/todoist/categories/project.jsx b/src/widgets/todoist/categories/project.jsx index b1e30b6fb..f98c8959f 100644 --- a/src/widgets/todoist/categories/project.jsx +++ b/src/widgets/todoist/categories/project.jsx @@ -1,10 +1,95 @@ -const groupTasksByProjectId = () => { - const groupedTasks = {}; - tasks.forEach((task) => { - if (!groupedTasks[task.project_id]) { - groupedTasks[task.project_id] = []; - } - groupedTasks[task.project_id].push(task); +import { useEffect, useState } from "react"; +import { useTranslation } from "next-i18next"; + +import useWidgetAPI from "../../../utils/proxy/use-widget-api"; +import Error from "../../../components/services/widget/error"; +import Agenda from "../agenda"; +import { DateTime } from "luxon"; + +export default function Project({ widget }) { + const { t } = useTranslation(); + + // Fetch projects data unconditionally + const { data: projectsData, error: projectsError } = useWidgetAPI(widget, "getAllProjects"); + + // Fetch tasks for the specific project unconditionally + const { data: tasksData, error: tasksError } = useWidgetAPI(widget, "getAllActiveTasks", { + refreshInterval: widget.refreshInterval || 300000, // 5 minutes, use default if not provided }); - return Object.values(groupedTasks); -}; + + // State to hold tasks + const [tasks, setTasks] = useState([]); + + useEffect(() => { + // Check for errors + if (projectsError) { + return; + } + + // Check if projectsData is available + if (!projectsData) { + return; + } + + // Find the project with the given name + const project = projectsData.find((project) => project.name === widget.project_name); + + // Check if project exists + if (!project) { + return; + } + + // Extract project ID and color + const projectId = project.id; + const projectColor = widget.color || project.color || "blue"; // Default color if not provided + + // Check for tasks error + if (tasksError) { + return; + } + + // Process tasks data and set tasks + if (tasksData && tasksData.length > 0) { + const tasksToAdd = tasksData + .filter((task) => task.project_id === projectId) // Filter tasks by project ID + .slice(0, widget.maxTasks || tasksData.length) + .map((task) => ({ + title: task.content || t("Untitled Task by Label"), + date: task.due ? DateTime.fromISO(task.due.date, { zone: widget.timeZone }).toJSDate() : null, + color: projectColor, // Assign project color to task + description: task.tags ? task.tags.join(", ") : "", + url: task.url, + id: task.id, + })); + + // Update the tasks state + setTasks(tasksToAdd); + } + }, [projectsData, projectsError, tasksData, tasksError, widget, t]); + + // Check for tasks error and display error component if not hidden + if (tasksError && !widget.hideErrors) { + return ; + } + + // Check for projects error and display error component + if (projectsError) { + return ; + } + + // If projectsData is not yet available, return null or loading indicator + if (!projectsData) { + return null; // or return a loading indicator + } + + // Find the project with the given name + const project = projectsData.find((project) => project.name === widget.project_name); + + // If project does not exist, display error component + if (!project) { + return ; + } + + // Render the Agenda component with tasks + return ; +} diff --git a/src/widgets/todoist/event.jsx b/src/widgets/todoist/event.jsx index 52208fcfd..0a8756848 100644 --- a/src/widgets/todoist/event.jsx +++ b/src/widgets/todoist/event.jsx @@ -24,6 +24,8 @@ export default function Event({ task, colorVariants }) { ); }; + const formatDate = (date) => DateTime.fromJSDate(date).toFormat("LLL dd") // Format to month and date (e.g., Jan 01); + return (
{task.date && ( - - {DateTime.fromJSDate(task.date) - .setLocale(i18n.language) - .toLocaleString(DateTime.TIME_24_SIMPLE)} - + {formatDate(task.date)} )} diff --git a/src/widgets/todoist/widget.js b/src/widgets/todoist/widget.js index c4e3f2986..8bd48c5b1 100644 --- a/src/widgets/todoist/widget.js +++ b/src/widgets/todoist/widget.js @@ -9,11 +9,25 @@ const widget = { method: "GET", endpoint: "tasks", }, + getAllProjects: { + method: "GET", + endpoint: "projects", + }, + getTasksWithCustomFilter: { + method: "GET", + endpoint: "tasks", + params: ["filter"] + }, getTasksWithLabel: { method: "GET", endpoint: "tasks", params: ["label"] }, + getTasksWithProject: { + method: "GET", + endpoint: "tasks", + params: ["project_id"] + }, }, };