Fix recur instance done (#727)

* Fix recur instance done

* Fix completed not showing
This commit is contained in:
Chris 2025-12-19 17:37:04 +02:00 committed by GitHub
parent 3fe694d1da
commit b6ecdbec90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 89 additions and 12 deletions

View file

@ -333,7 +333,8 @@ async function fetchTasksCompletedToday(userId, userTimezone) {
const safeTimezone = getSafeTimezone(userTimezone);
const todayBounds = getTodayBoundsInUTC(safeTimezone);
return await Task.findAll({
// Fetch regular completed tasks
const regularCompletedTasks = await Task.findAll({
where: {
user_id: userId,
status: Task.STATUS.DONE,
@ -345,8 +346,57 @@ async function fetchTasksCompletedToday(userId, userTimezone) {
},
},
include: getTaskIncludeConfig(),
order: [['completed_at', 'DESC']],
});
// Fetch recurring tasks completed today via recurring_completions table
const { RecurringCompletion } = require('../../../models');
const recurringCompletions = await RecurringCompletion.findAll({
where: {
completed_at: {
[Op.gte]: todayBounds.start,
[Op.lte]: todayBounds.end,
},
skipped: false,
},
include: [
{
model: Task,
as: 'Task',
where: {
user_id: userId,
parent_task_id: null,
},
include: getTaskIncludeConfig(),
},
],
});
// Extract the tasks from recurring completions and add completed_at and status
const recurringCompletedTasks = recurringCompletions.map((rc) => {
const task = rc.Task;
// Add virtual completed_at and status for display purposes
task.dataValues.completed_at = rc.completed_at;
task.dataValues.status = Task.STATUS.DONE;
// Also set the direct property to ensure it's accessible
task.status = Task.STATUS.DONE;
task.completed_at = rc.completed_at;
return task;
});
// Combine both lists
const allCompletedTasks = [
...regularCompletedTasks,
...recurringCompletedTasks,
];
// Sort by completed_at DESC
allCompletedTasks.sort((a, b) => {
const aTime = a.completed_at || a.dataValues.completed_at;
const bTime = b.completed_at || b.dataValues.completed_at;
return new Date(bTime) - new Date(aTime);
});
return allCompletedTasks;
}
module.exports = {

View file

@ -338,7 +338,10 @@ const GroupedTaskList: React.FC<GroupedTaskListProps> = ({
{/* Day column tasks */}
<div className="space-y-1.5">
{dayTasks.map((task) => (
<div key={task.id}>
<div
key={task.id}
className="relative hover:z-[10000] focus-within:z-[10000]"
>
<TaskItem
task={task}
onTaskUpdate={
@ -437,7 +440,7 @@ const GroupedTaskList: React.FC<GroupedTaskListProps> = ({
{projectTasks.map((task) => (
<div
key={task.id}
className="task-item-wrapper transition-all duration-200 ease-in-out"
className="task-item-wrapper transition-all duration-200 ease-in-out relative hover:z-[10000] focus-within:z-[10000]"
>
<TaskItem
task={task}
@ -459,7 +462,7 @@ const GroupedTaskList: React.FC<GroupedTaskListProps> = ({
: standaloneTask.map((task) => (
<div
key={task.id}
className="task-item-wrapper transition-all duration-200 ease-in-out"
className="task-item-wrapper transition-all duration-200 ease-in-out relative hover:z-[10000] focus-within:z-[10000]"
>
<TaskItem
task={task}
@ -488,7 +491,7 @@ const GroupedTaskList: React.FC<GroupedTaskListProps> = ({
>
{/* Show template only if it's not virtual */}
{!isVirtualTemplate && (
<div className="relative">
<div className="relative hover:z-[10000] focus-within:z-[10000]">
<div className="flex items-center">
<div className="flex-1">
<TaskItem
@ -569,7 +572,7 @@ const GroupedTaskList: React.FC<GroupedTaskListProps> = ({
.map((instance) => (
<div
key={instance.id}
className="opacity-75 hover:opacity-100 transition-opacity"
className="opacity-75 hover:opacity-100 focus-within:opacity-100 transition-opacity relative hover:z-[10000] focus-within:z-[10000]"
>
<TaskItem
task={instance}

View file

@ -179,7 +179,9 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
>
{/* Full view (md and larger) */}
<div className="hidden md:flex flex-col md:flex-row md:items-center md:relative">
<div className="flex items-center space-x-3 mb-2 md:mb-0 flex-1 min-w-0 pr-44">
<div
className={`flex items-center space-x-3 mb-2 md:mb-0 flex-1 min-w-0 ${!isUpcomingView ? 'pr-44' : ''}`}
>
<div className="hidden">
<TaskPriorityIcon
priority={task.priority}

View file

@ -43,7 +43,7 @@ const TaskList: React.FC<TaskListProps> = ({
filteredTasks.map((task) => (
<div
key={task.id}
className="task-item-wrapper transition-all duration-200 ease-in-out overflow-visible"
className="task-item-wrapper transition-all duration-200 ease-in-out overflow-visible relative hover:z-[10000] focus-within:z-[10000]"
data-testid={`task-item-${task.id}`}
>
<TaskItem

View file

@ -333,7 +333,9 @@ const TaskStatusControl: React.FC<TaskStatusControlProps> = ({
const completionButtonLabel = statusDisplay.label;
return (
<div className={`relative ${className}`}>
<div
className={`relative ${completionMenuOpen ? 'z-[10000]' : ''} ${className}`}
>
<div
className={`inline-flex items-stretch ${containerRoundedClass} border ${statusBorderColorClass} overflow-hidden ${hoverRevealQuickActions ? 'group' : ''}`}
ref={desktopCompletionMenuRef}
@ -410,7 +412,7 @@ const TaskStatusControl: React.FC<TaskStatusControlProps> = ({
</div>
{completionMenuOpen === 'desktop' && (
<div
className={`absolute right-0 top-full mt-1 w-48 bg-white dark:bg-gray-900 border ${statusBorderColorClass} rounded-lg shadow-lg z-[9999]`}
className={`absolute right-0 top-full mt-1 w-48 bg-white dark:bg-gray-900 border ${statusBorderColorClass} rounded-lg shadow-lg z-[9999] opacity-100`}
>
{renderStatusMenuOptions('desktop')}
</div>
@ -497,7 +499,7 @@ const TaskStatusControl: React.FC<TaskStatusControlProps> = ({
</div>
{completionMenuOpen === 'mobile' && (
<div
className={`absolute right-0 top-full mt-1 w-48 bg-white dark:bg-gray-900 border ${statusBorderColorClass} rounded-lg shadow-lg z-[9999]`}
className={`absolute right-0 top-full mt-1 w-48 bg-white dark:bg-gray-900 border ${statusBorderColorClass} rounded-lg shadow-lg z-[9999] opacity-100`}
>
{renderStatusMenuOptions('mobile')}
</div>

View file

@ -1024,6 +1024,26 @@ const TasksToday: React.FC = () => {
// The updatedTask is already the result of the API call from TaskItem
// Use the centralized task update handler to update UI optimistically
await handleTaskUpdate(updatedTask);
// Check if this was a recurring task completion that needs refresh
// Recurring tasks get advanced after completion, so they won't appear in completed list
// without a refetch
const isRecurringParent =
updatedTask.recurrence_type &&
updatedTask.recurrence_type !== 'none' &&
!updatedTask.recurring_parent_id;
if (isRecurringParent) {
// Refetch tasks to get the updated completed list for recurring tasks
const result = await fetchTasks('?type=today');
if (isMounted.current) {
setMetrics((prevMetrics) => ({
...prevMetrics,
tasks_completed_today:
result.tasks_completed_today || [],
}));
}
}
} catch (error) {
console.error('Error toggling task completion:', error);
}