parent
679aff4a8d
commit
57e7aa6b2b
@ -0,0 +1,46 @@
|
|||||||
|
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",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Agenda({ tasks }) {
|
||||||
|
return (
|
||||||
|
<div className="pl-1 pr-1 pb-1">
|
||||||
|
<div className={classNames("flex flex-col", !tasks.length && "animate-pulse")}>
|
||||||
|
{tasks.map((task) => (
|
||||||
|
<Event
|
||||||
|
key={task.id} // Use the unique task ID as the key
|
||||||
|
task={task} // Pass the task object to the Event component
|
||||||
|
colorVariants={colorVariants} // Pass color variants to Event component
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
const [tasks, setEvents] = useState([]); // State to hold events
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!tasksError && tasksData && tasksData.length > 0) { // Check if tasksData is not empty
|
||||||
|
// 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
|
||||||
|
description: task.tags ? task.tags.join(", ") : "",
|
||||||
|
url: task.url,
|
||||||
|
id: task.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Update the events state
|
||||||
|
setEvents(tasksToAdd);
|
||||||
|
}
|
||||||
|
}, [tasksData, tasksError, widget, setEvents, t]);
|
||||||
|
|
||||||
|
const error = tasksError ?? tasksData?.error;
|
||||||
|
if (error && !widget.hideErrors) {
|
||||||
|
return <Error error={{ message: `${widget.type}: ${error.message ?? error}` }} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the Agenda component if tasks is not empty
|
||||||
|
return <Agenda tasks={tasks} />;
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
const groupTasksByProjectId = () => {
|
||||||
|
const groupedTasks = {};
|
||||||
|
tasks.forEach((task) => {
|
||||||
|
if (!groupedTasks[task.project_id]) {
|
||||||
|
groupedTasks[task.project_id] = [];
|
||||||
|
}
|
||||||
|
groupedTasks[task.project_id].push(task);
|
||||||
|
});
|
||||||
|
return Object.values(groupedTasks);
|
||||||
|
};
|
@ -0,0 +1,44 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
import Container from "components/services/widget/container";
|
||||||
|
import Block from "components/services/widget/block"; // Assuming this component renders the category name as a block-level element
|
||||||
|
|
||||||
|
export default function Component({ service }) {
|
||||||
|
const { widget } = service;
|
||||||
|
|
||||||
|
// Load categories
|
||||||
|
const categories = useMemo(
|
||||||
|
() =>
|
||||||
|
widget.categories
|
||||||
|
?.filter((category) => category?.sort)
|
||||||
|
.map((category) => ({
|
||||||
|
service: dynamic(() => import(`./categories/${category.sort}`)),
|
||||||
|
widget: { ...widget, ...category },
|
||||||
|
categoryName: category.category_name, // Add categoryName property
|
||||||
|
})) ?? [],
|
||||||
|
[widget]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container service={service}>
|
||||||
|
<div className="flex flex-col w-full">
|
||||||
|
<div className="sticky top-0">
|
||||||
|
{categories.map((category) => {
|
||||||
|
const Integration = category.service;
|
||||||
|
const key = `integration-${category.widget.type}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={key}>
|
||||||
|
<Block value={category.categoryName} />
|
||||||
|
<Integration
|
||||||
|
widget={category.widget}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { IoMdCheckmarkCircleOutline } from "react-icons/io";
|
||||||
|
|
||||||
|
export default function Event({ task, colorVariants }) {
|
||||||
|
const [hover, setHover] = useState(false);
|
||||||
|
const { i18n } = useTranslation(); // Ensure you're getting 't' from useTranslation()
|
||||||
|
|
||||||
|
const renderEventTitle = () => {
|
||||||
|
if (task.url) {
|
||||||
|
return (
|
||||||
|
<a href={task.url} target="_blank" rel="noopener noreferrer" className="flex-grow truncate">
|
||||||
|
{hover && task.additional ? task.additional : task.title}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex-grow truncate">
|
||||||
|
{hover && task.additional ? task.additional : task.title}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex flex-row text-theme-700 dark:text-theme-200 items-center text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1"
|
||||||
|
onMouseEnter={() => setHover(true)} // Change to setHover(true) and setHover(false)
|
||||||
|
onMouseLeave={() => setHover(false)} // Change to setHover(false)
|
||||||
|
key={`task-${task.id}`}
|
||||||
|
>
|
||||||
|
<span className="ml-2 w-10">
|
||||||
|
{task.date && (
|
||||||
|
<span>
|
||||||
|
{DateTime.fromJSDate(task.date)
|
||||||
|
.setLocale(i18n.language)
|
||||||
|
.toLocaleString(DateTime.TIME_24_SIMPLE)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span className="ml-2 h-2 w-2">
|
||||||
|
<span className={classNames("block w-2 h-2 rounded", colorVariants[task.color] ?? "gray")} />
|
||||||
|
</span>
|
||||||
|
<div className="ml-2 h-5 text-left relative truncate" style={{ width: "70%" }}>
|
||||||
|
<div className="absolute mt-0.5 text-xs">{renderEventTitle()}</div>
|
||||||
|
</div>
|
||||||
|
{task.isCompleted && (
|
||||||
|
<span className="text-xs mr-1 ml-auto z-10">
|
||||||
|
<IoMdCheckmarkCircleOutline />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import credentialedProxyHandler from "utils/proxy/handlers/credentialed";
|
||||||
|
|
||||||
|
const widget = {
|
||||||
|
api: "https://api.todoist.com/rest/v2/{endpoint}",
|
||||||
|
proxyHandler: credentialedProxyHandler,
|
||||||
|
|
||||||
|
mappings: {
|
||||||
|
getAllActiveTasks: {
|
||||||
|
method: "GET",
|
||||||
|
endpoint: "tasks",
|
||||||
|
},
|
||||||
|
getTasksWithLabel: {
|
||||||
|
method: "GET",
|
||||||
|
endpoint: "tasks",
|
||||||
|
params: ["label"]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default widget;
|
||||||
|
|
Loading…
Reference in new issue