Fix sprint timezone bug and add auto-rollover
- Sprint end dates now treated as end-of-day (23:59:59) to handle timezone issues where sprints appeared empty before local midnight - Added auto-rollover: when a sprint ends, incomplete tasks automatically move to the next sprint, ended sprint marked as completed - Applied fix to both main page and BacklogView
This commit is contained in:
parent
bea107b4e9
commit
02d19a1afb
@ -359,6 +359,7 @@ export default function Home() {
|
|||||||
setCurrentUser,
|
setCurrentUser,
|
||||||
syncFromServer,
|
syncFromServer,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
updateSprint,
|
||||||
} = useTaskStore()
|
} = useTaskStore()
|
||||||
|
|
||||||
const [newTaskOpen, setNewTaskOpen] = useState(false)
|
const [newTaskOpen, setNewTaskOpen] = useState(false)
|
||||||
@ -583,15 +584,17 @@ export default function Home() {
|
|||||||
}, [selectedTask])
|
}, [selectedTask])
|
||||||
|
|
||||||
// Get current active sprint (across all projects)
|
// Get current active sprint (across all projects)
|
||||||
|
// Treat end date as end-of-day (23:59:59) to handle timezone issues
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const currentSprint = sprints.find((s) =>
|
const currentSprint = sprints.find((s) => {
|
||||||
s.status === 'active' &&
|
if (s.status !== 'active') return false
|
||||||
new Date(s.startDate) <= now &&
|
const sprintEnd = new Date(s.endDate)
|
||||||
new Date(s.endDate) >= now
|
sprintEnd.setHours(23, 59, 59, 999)
|
||||||
)
|
return new Date(s.startDate) <= now && sprintEnd >= now
|
||||||
|
})
|
||||||
|
|
||||||
// Filter tasks to only show current sprint tasks in Kanban (from ALL projects)
|
// Filter tasks to only show current sprint tasks in Kanban (from ALL projects)
|
||||||
const sprintTasks = currentSprint
|
const sprintTasks = currentSprint
|
||||||
? tasks.filter((t) => {
|
? tasks.filter((t) => {
|
||||||
if (t.sprintId !== currentSprint.id) return false
|
if (t.sprintId !== currentSprint.id) return false
|
||||||
// Apply search filter
|
// Apply search filter
|
||||||
@ -604,6 +607,51 @@ export default function Home() {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
: []
|
: []
|
||||||
|
|
||||||
|
// Auto-rollover: Move incomplete tasks from ended sprints to next sprint
|
||||||
|
useEffect(() => {
|
||||||
|
if (!authReady || sprints.length === 0) return
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const endedSprints = sprints.filter((s) => {
|
||||||
|
if (s.status !== 'active') return false
|
||||||
|
const sprintEnd = new Date(s.endDate)
|
||||||
|
sprintEnd.setHours(23, 59, 59, 999)
|
||||||
|
return sprintEnd < now
|
||||||
|
})
|
||||||
|
|
||||||
|
if (endedSprints.length === 0) return
|
||||||
|
|
||||||
|
// Find next sprint (earliest start date that's in the future or active)
|
||||||
|
const nextSprint = sprints
|
||||||
|
.filter((s) => s.status === 'planning' || (s.status === 'active' && !endedSprints.find((e) => e.id === s.id)))
|
||||||
|
.sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime())[0]
|
||||||
|
|
||||||
|
if (!nextSprint) return
|
||||||
|
|
||||||
|
// Process each ended sprint
|
||||||
|
endedSprints.forEach((endedSprint) => {
|
||||||
|
const incompleteTasks = tasks.filter(
|
||||||
|
(t) => t.sprintId === endedSprint.id && t.status !== 'done' && t.status !== 'canceled' && t.status !== 'archived'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (incompleteTasks.length > 0) {
|
||||||
|
console.log(`Auto-rolling over ${incompleteTasks.length} tasks from "${endedSprint.name}" to "${nextSprint.name}"`)
|
||||||
|
|
||||||
|
// Move incomplete tasks to next sprint
|
||||||
|
incompleteTasks.forEach((task) => {
|
||||||
|
updateTask(task.id, { sprintId: nextSprint.id })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mark ended sprint as completed
|
||||||
|
updateSprint(endedSprint.id, { status: 'completed' })
|
||||||
|
} else {
|
||||||
|
// No incomplete tasks, just mark as completed
|
||||||
|
updateSprint(endedSprint.id, { status: 'completed' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [authReady, sprints, tasks, updateTask, updateSprint])
|
||||||
|
|
||||||
const activeKanbanTask = activeKanbanTaskId
|
const activeKanbanTask = activeKanbanTaskId
|
||||||
? sprintTasks.find((task) => task.id === activeKanbanTaskId)
|
? sprintTasks.find((task) => task.id === activeKanbanTaskId)
|
||||||
: null
|
: null
|
||||||
|
|||||||
@ -306,13 +306,14 @@ export function BacklogView({ searchQuery = "" }: BacklogViewProps) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Get current active sprint
|
// Get current active sprint
|
||||||
|
// Treat end date as end-of-day (23:59:59) to handle timezone issues
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const currentSprint = sprints.find(
|
const currentSprint = sprints.find((s) => {
|
||||||
(s) =>
|
if (s.status !== "active") return false
|
||||||
s.status === "active" &&
|
const sprintEnd = new Date(s.endDate)
|
||||||
new Date(s.startDate) <= now &&
|
sprintEnd.setHours(23, 59, 59, 999)
|
||||||
new Date(s.endDate) >= now
|
return new Date(s.startDate) <= now && sprintEnd >= now
|
||||||
)
|
})
|
||||||
|
|
||||||
// Get other sprints (not current)
|
// Get other sprints (not current)
|
||||||
const otherSprints = sprints.filter((s) => s.id !== currentSprint?.id)
|
const otherSprints = sprints.filter((s) => s.id !== currentSprint?.id)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user