import { useState } from "react";
import {
useUpdate,
useNotify,
useCreate,
useGetIdentity,
useLocaleState,
useTranslate,
} from "ra-core";
import { Check, Pencil, Clock } from "lucide-react";
import { DataTable } from "@/components/ds/admin/data-table";
import { ReferenceField } from "@/components/ds/admin/reference-field";
import { TextField } from "@/components/ds/admin/text-field";
import { Button } from "@/components/ds/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ds/ui/tooltip";
import { getRelativeDueDate, parseLocalDate } from "@/lib/date-utils";
import type { Task, TaskSummary } from "../types";
import { TaskPriorityBadge } from "./TaskPriorityBadge";
import { TaskStatusBadge } from "./TaskStatusBadge";
import { TaskEdit } from "./TaskEdit";
import { TaskTypeIcon } from "./TaskTypeIcon";
const RelatedEntityField = ({ record }: { record: Task | TaskSummary }) => {
const translate = useTranslate();
const getEntityName = () => {
if (record.contact_id) {
// For contacts, the ReferenceField will show first_name + last_name
return (
);
}
if (record.company_id) {
return (
);
}
if (record.deal_id) {
return (
);
}
return —;
};
const title = record.contact_id
? translate("crm.filter.contact")
: record.company_id
? translate("crm.filter.company")
: record.deal_id
? translate("crm.filter.deal")
: "";
return (
{getEntityName()}
);
};
const DueDateField = ({
record,
translate,
locale,
}: {
record: Task | TaskSummary;
translate: ReturnType;
locale?: string;
}) => {
const isCompleted = record.status === "done" || record.status === "cancelled";
const { text, isOverdue } = getRelativeDueDate(record.due_date, isCompleted, {
translate,
locale,
});
// Parse date as local date to avoid timezone shift (same logic as getRelativeDueDate)
const dueDate = record.due_date ? parseLocalDate(record.due_date) : null;
const formattedDate = dueDate ? dueDate.toLocaleDateString(locale) : "";
return (
{text}
{dueDate && !isCompleted && (
{formattedDate}
)}
);
};
const TaskActions = ({
record,
onEdit,
}: {
record: Task;
onEdit: (taskId: number) => void;
}) => {
const [update] = useUpdate();
const [create] = useCreate();
const notify = useNotify();
const { identity } = useGetIdentity();
const translate = useTranslate();
const [locale] = useLocaleState();
// Create a task note for audit trail
const createTaskNote = (text: string) => {
if (!identity?.id) return;
// Use server-based UTC timestamp (single source of truth)
// Avoids client-side time issues (wrong machine time, timezone errors)
const date = new Date().toISOString();
create(
"taskNotes",
{
data: {
task_id: record.id,
text,
date,
sales_id: identity.id,
status: "cold",
},
},
{
onError: (error) => {
console.error("Failed to create task note:", error);
},
},
);
};
const handleMarkComplete = (e: React.MouseEvent) => {
e.stopPropagation();
update(
"tasks",
{
id: record.id,
data: {
status: "done",
done_date: new Date().toISOString(),
updated_at: new Date().toISOString(),
},
previousData: record,
},
{
onSuccess: () => {
notify(translate("crm.task.notification.marked_complete"), {
type: "success",
});
createTaskNote(translate("crm.task.note.marked_complete_quick"));
},
},
);
};
const handleEdit = (e: React.MouseEvent) => {
e.stopPropagation();
onEdit(record.id as number);
};
const handleSnooze = (e: React.MouseEvent) => {
e.stopPropagation();
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
let newDueDateString: string;
let noteText: string;
let notificationText: string;
if (!record.due_date) {
// No due date, set to tomorrow
newDueDateString = tomorrow.toISOString().slice(0, 10);
noteText = translate("crm.task.note.snoozed_to_date", {
date: tomorrow.toLocaleDateString(locale),
});
notificationText = translate("crm.task.notification.snoozed_tomorrow");
} else {
const dueDate = parseLocalDate(record.due_date);
if (!dueDate) {
newDueDateString = tomorrow.toISOString().slice(0, 10);
noteText = translate("crm.task.note.snoozed_to_date", {
date: tomorrow.toLocaleDateString(locale),
});
notificationText = translate("crm.task.notification.snoozed_tomorrow");
} else {
const isOverdueOrDueToday = dueDate <= today;
if (isOverdueOrDueToday) {
newDueDateString = tomorrow.toISOString().slice(0, 10);
noteText = translate("crm.task.note.snoozed_to_date", {
date: tomorrow.toLocaleDateString(locale),
});
notificationText = translate(
"crm.task.notification.snoozed_tomorrow",
);
} else {
// Add 1 day to the due date
const newDueDate = new Date(dueDate);
newDueDate.setDate(newDueDate.getDate() + 1);
newDueDateString = newDueDate.toISOString().slice(0, 10);
noteText = translate("crm.task.note.postponed_to_date", {
date: newDueDate.toLocaleDateString(locale),
});
notificationText = translate("crm.task.notification.postponed_day");
}
}
}
update(
"tasks",
{
id: record.id,
data: {
due_date: newDueDateString,
updated_at: new Date().toISOString(),
},
previousData: record,
},
{
onSuccess: () => {
notify(notificationText, { type: "success" });
createTaskNote(noteText);
},
},
);
};
const isCompleted = record.status === "done" || record.status === "cancelled";
// Determine smart button label
const today = new Date();
today.setHours(0, 0, 0, 0);
let isOverdueOrDueToday = true;
if (record.due_date) {
const labelDueDate = parseLocalDate(record.due_date);
if (labelDueDate) {
isOverdueOrDueToday = labelDueDate <= today;
}
}
const snoozeLabel = isOverdueOrDueToday
? translate("crm.task.action.snooze_tomorrow")
: translate("crm.task.action.postpone_day");
return (
{!isCompleted && (
{translate("crm.task.action.mark_complete")}
)}
{translate("crm.task.action.edit")}
{!isCompleted && (
{snoozeLabel}
)}
);
};
export const TaskListTable = () => {
const [editingTaskId, setEditingTaskId] = useState(null);
const translate = useTranslate();
const [locale] = useLocaleState();
const getRowClassName = (record: Task) => {
const isCompleted =
record.status === "done" || record.status === "cancelled";
const { isOverdue } = getRelativeDueDate(record.due_date, isCompleted, {
translate,
locale,
});
// Add 'group' class for hover actions, plus overdue styling
const baseClass = "group";
if (isCompleted) return baseClass;
return isOverdue
? `${baseClass} bg-destructive/10 hover:bg-destructive/20`
: baseClass;
};
return (
<>
(
)}
/>
}
/>
(
)}
/>
(
)}
/>
}
/>
(
)}
/>
{editingTaskId && (
setEditingTaskId(null)}
/>
)}
>
);
};