diff --git a/app/_components/FeatureComponents/Kanban/KanbanCard.tsx b/app/_components/FeatureComponents/Kanban/KanbanCard.tsx
index f0f11b7cb..c0f6cc8fb 100644
--- a/app/_components/FeatureComponents/Kanban/KanbanCard.tsx
+++ b/app/_components/FeatureComponents/Kanban/KanbanCard.tsx
@@ -78,6 +78,7 @@ const KanbanCardComponent = ({
const [showTimeEntriesModal, setShowTimeEntriesModal] = useState(false);
const [showStatusSheet, setShowStatusSheet] = useState(false);
const hideMobileStatusDropdown = user?.hideMobileStatusDropdown === "enable";
+ const hideTimeTrackingOnCards = user?.hideTimeTrackingOnCards === "enable";
const kanbanItemHook = useKanbanItem({
checklist,
@@ -157,7 +158,7 @@ const KanbanCardComponent = ({
isOpen={showTimeEntriesModal}
onClose={() => setShowTimeEntriesModal(false)}
timeEntries={item.timeEntries}
- checklistId={checklist.uuid || checklistId}
+ checklistId={checklistId}
itemId={item.id}
category={category}
onUpdate={onUpdate}
@@ -255,29 +256,33 @@ const KanbanCardComponent = ({
)}
-
-
- {item.timeEntries && item.timeEntries.length > 0 && (
-
e.stopPropagation()}>
-
+ setShowTimeEntriesModal(true)}
+ onTimerToggle={kanbanItemHook.handleTimerToggle}
+ onAddManualTime={kanbanItemHook.handleAddManualTime}
/>
-
+
+ {item.timeEntries && item.timeEntries.length > 0 && (
+ e.stopPropagation()}>
+ setShowTimeEntriesModal(true)}
+ />
+
+ )}
+ >
)}
{item.recurrence && (
diff --git a/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx b/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx
index 5fa0c41cb..1a6c26e36 100644
--- a/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx
+++ b/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx
@@ -22,6 +22,12 @@ import { useTranslations } from "next-intl";
import { KanbanPriorityLevel } from "@/app/_types/enums";
import { KanbanCardDetailProperties } from "./KanbanCardDetailProperties";
import { KanbanCardDetailSubtasks } from "./KanbanCardDetailSubtasks";
+import { KanbanItemTimer } from "./KanbanItemTimer";
+import { TimeEntriesAccordion } from "./TimeEntriesAccordion";
+import { TimeEntriesModal } from "./TimeEntriesModal";
+import { useKanbanItem } from "@/app/_hooks/kanban/useKanbanItem";
+import { useAppMode } from "@/app/_providers/AppModeProvider";
+import { formatTimerTime } from "@/app/_utils/kanban/index";
import { DEFAULT_KANBAN_STATUSES } from "@/app/_consts/kanban";
interface KanbanCardDetailProps {
@@ -78,7 +84,9 @@ export const KanbanCardDetail = ({
}: KanbanCardDetailProps) => {
const t = useTranslations();
const { permissions } = usePermissions();
- const { formatDateTimeString } = usePreferredDateTime();
+ const { usersPublicData } = useAppMode();
+ const { formatDateTimeString, formatDateString, formatTimeString } =
+ usePreferredDateTime();
const statuses = checklist.statuses || DEFAULT_KANBAN_STATUSES;
const defaultStatusId =
[...statuses].sort((a, b) => a.order - b.order)[0]?.id || "";
@@ -98,6 +106,15 @@ export const KanbanCardDetail = ({
const [estimatedTimeInput, setEstimatedTimeInput] = useState(initialItem.estimatedTime?.toString() || "");
const [availableUsers, setAvailableUsers] = useState<{ username: string; avatarUrl?: string }[]>([]);
const [boardIsShared, setBoardIsShared] = useState(false);
+ const [showTimeEntriesModal, setShowTimeEntriesModal] = useState(false);
+
+ const kanbanItemHook = useKanbanItem({
+ checklist,
+ item,
+ checklistId,
+ category,
+ onUpdate,
+ });
useEffect(() => {
setItem(initialItem);
@@ -379,16 +396,53 @@ export const KanbanCardDetail = ({
await _saveField({ estimatedTime: hours.toString() });
};
+ const timeTrackingContent = (
+
+
+ {item.timeEntries && item.timeEntries.length > 0 && (
+ setShowTimeEntriesModal(true)}
+ />
+ )}
+
+ );
+
return (
-
+ <>
+ {showTimeEntriesModal && item.timeEntries && (
+ setShowTimeEntriesModal(false)}
+ timeEntries={item.timeEntries}
+ checklistId={checklistId}
+ itemId={item.id}
+ category={category}
+ onUpdate={onUpdate}
+ usersPublicData={usersPublicData}
+ />
+ )}
+
{isEditing ? (
@@ -545,9 +599,11 @@ export const KanbanCardDetail = ({
onEstimatedTimeSave={handleEstimatedTimeSave}
formatDateTimeString={formatDateTimeString}
onStatusChange={handleStatusChange}
+ timeTracking={timeTrackingContent}
/>
-
+
+ >
);
};
diff --git a/app/_components/FeatureComponents/Kanban/KanbanCardDetailProperties.tsx b/app/_components/FeatureComponents/Kanban/KanbanCardDetailProperties.tsx
index e236d74a6..a590087b9 100644
--- a/app/_components/FeatureComponents/Kanban/KanbanCardDetailProperties.tsx
+++ b/app/_components/FeatureComponents/Kanban/KanbanCardDetailProperties.tsx
@@ -42,6 +42,7 @@ interface KanbanCardDetailPropertiesProps {
onEstimatedTimeChange: (v: string) => void;
onEstimatedTimeSave: () => void;
formatDateTimeString: (v: string) => string;
+ timeTracking?: ReactNode;
}
interface PropertySectionProps {
@@ -104,6 +105,7 @@ export const KanbanCardDetailProperties = ({
onEstimatedTimeChange,
onEstimatedTimeSave,
formatDateTimeString,
+ timeTracking,
}: KanbanCardDetailPropertiesProps) => {
const t = useTranslations();
@@ -288,6 +290,12 @@ export const KanbanCardDetailProperties = ({
>
)}
+ {timeTracking && (
+
+ {timeTracking}
+
+ )}
+
{metadata.length > 0 && (
hideConnectionIndicator: user?.hideConnectionIndicator || "disable",
hideStatusOnCards: user?.hideStatusOnCards || "disable",
hideMobileStatusDropdown: user?.hideMobileStatusDropdown || "disable",
+ hideTimeTrackingOnCards: user?.hideTimeTrackingOnCards || "disable",
codeBlockStyle: user?.codeBlockStyle || "default",
});
@@ -171,6 +173,7 @@ export const UserPreferencesTab = ({ noteCategories, localeOptions }: SettingsTa
const hasKanbanChanges = hasChanges([
"hideStatusOnCards",
"hideMobileStatusDropdown",
+ "hideTimeTrackingOnCards",
]);
const validateAndSave = async >(
@@ -952,7 +955,11 @@ export const UserPreferencesTab = ({ noteCategories, localeOptions }: SettingsTa