feat(PBI-79/ST-1333): active-sprint null-contract + clearActiveSprintAction
- lib/user-settings.ts: activeSprints values nullable in Zod-schema. Key-aanwezigheid heeft nu betekenis (key+null = bewust geen sprint; key ontbreekt = fallback-cascade). - lib/active-sprint.ts: nieuwe readStoredActiveSprintState helper + resolveActiveSprint respecteert expliciet 'cleared' state zonder fallback. clearActiveSprintInSettings schrijft null i.p.v. de key te verwijderen. - actions/active-sprint.ts: nieuwe clearActiveSprintAction met auth + membership-check. - components/shared/sprint-switcher.tsx: '— Geen actieve sprint —'-optie in dropdown, disabled wanneer er geen actieve sprint is. - Tests: nieuwe active-sprint.test.ts (resolver-paden + clear), active-sprint-action.test.ts (action-laag), uitbreiding user-settings.test.ts. Plan: docs/plans/PBI-79-backlog-sprint-workflow.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bf7162a5fc
commit
2af6f24598
9 changed files with 939 additions and 16 deletions
|
|
@ -40,12 +40,20 @@ async function notifyUserSettings(
|
|||
`
|
||||
}
|
||||
|
||||
export async function getActiveSprintIdFromSettings(
|
||||
userId: string,
|
||||
type StoredActiveSprintState =
|
||||
| { kind: 'unset' }
|
||||
| { kind: 'cleared' }
|
||||
| { kind: 'set'; sprintId: string }
|
||||
|
||||
export function readStoredActiveSprintState(
|
||||
settings: UserSettings,
|
||||
productId: string,
|
||||
): Promise<string | null> {
|
||||
const settings = await readSettings(userId)
|
||||
return settings.layout?.activeSprints?.[productId] ?? null
|
||||
): StoredActiveSprintState {
|
||||
const map = settings.layout?.activeSprints
|
||||
if (!map || !(productId in map)) return { kind: 'unset' }
|
||||
const value = map[productId]
|
||||
if (value === null) return { kind: 'cleared' }
|
||||
return { kind: 'set', sprintId: value }
|
||||
}
|
||||
|
||||
export async function setActiveSprintInSettings(
|
||||
|
|
@ -71,10 +79,10 @@ export async function clearActiveSprintInSettings(
|
|||
productId: string,
|
||||
): Promise<void> {
|
||||
const current = await readSettings(userId)
|
||||
const existing = current.layout?.activeSprints
|
||||
if (!existing || !(productId in existing)) return
|
||||
const nextActiveSprints = { ...existing }
|
||||
delete nextActiveSprints[productId]
|
||||
const nextActiveSprints: Record<string, string | null> = {
|
||||
...(current.layout?.activeSprints ?? {}),
|
||||
[productId]: null,
|
||||
}
|
||||
const next: UserSettings = {
|
||||
...current,
|
||||
layout: { ...current.layout, activeSprints: nextActiveSprints },
|
||||
|
|
@ -89,10 +97,14 @@ export async function resolveActiveSprint(
|
|||
productId: string,
|
||||
userId: string,
|
||||
): Promise<ActiveSprint | null> {
|
||||
const stored = await getActiveSprintIdFromSettings(userId, productId)
|
||||
if (stored) {
|
||||
const settings = await readSettings(userId)
|
||||
const state = readStoredActiveSprintState(settings, productId)
|
||||
|
||||
if (state.kind === 'cleared') return null
|
||||
|
||||
if (state.kind === 'set') {
|
||||
const sprint = await prisma.sprint.findFirst({
|
||||
where: { id: stored, product_id: productId },
|
||||
where: { id: state.sprintId, product_id: productId },
|
||||
select: { id: true, code: true, status: true },
|
||||
})
|
||||
if (sprint) return sprint
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const DevToolsPrefs = z.object({
|
|||
|
||||
const LayoutPrefs = z.object({
|
||||
splitPanePositions: z.record(z.string(), z.array(z.number())).optional(),
|
||||
activeSprints: z.record(z.string(), z.string()).optional(),
|
||||
activeSprints: z.record(z.string(), z.string().nullable()).optional(),
|
||||
}).strict()
|
||||
|
||||
export const UserSettingsSchema = z.object({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue