feat(ST-1138): mobile Solo-pagina + verify TaskDetailDialog (T-331/T-332/T-333)
- app/(mobile)/m/products/[id]/solo/page.tsx — hergebruikt SoloBoard 1:1 met desktop. 3-koloms-kanban blijft, NoActiveSprint-fallback ongewijzigd - T-332 verify-only: TaskDetailDialog regel 383 gebruikt entityDialogContentClasses → mobile-fullscreen erft automatisch uit ST-1133 - Tests: regressie-vangnet op SoloBoard-hergebruik, requireSession, NoActiveSprint, en op TaskDetailDialog-className-wiring (geen override) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5b42740461
commit
b327fbdf09
2 changed files with 155 additions and 0 deletions
120
app/(mobile)/m/products/[id]/solo/page.tsx
Normal file
120
app/(mobile)/m/products/[id]/solo/page.tsx
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// PBI-11 / ST-1138: Mobile Solo Paneel — wraps de bestaande SoloBoard zonder
|
||||
// content-aanpassingen. 3-koloms-kanban blijft (overflow-x scrollt zijwaarts).
|
||||
// TaskDetailDialog krijgt full-screen-mobile via gedeelde
|
||||
// entityDialogContentClasses (beslissing A in docs/plans/PBI-11-mobile-shell.md;
|
||||
// ingebouwd via ST-1133/T-317).
|
||||
|
||||
import { notFound } from 'next/navigation'
|
||||
import { getAccessibleProduct } from '@/lib/product-access'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { requireSession } from '@/lib/auth-guard'
|
||||
import { SoloBoard } from '@/components/solo/solo-board'
|
||||
import { NoActiveSprint } from '@/components/solo/no-active-sprint'
|
||||
import type { SoloTask } from '@/components/solo/solo-board'
|
||||
import type { UnassignedStory } from '@/components/solo/unassigned-stories-sheet'
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ id: string }>
|
||||
}
|
||||
|
||||
export default async function MobileSoloProductPage({ params }: Props) {
|
||||
const { id } = await params
|
||||
const session = await requireSession()
|
||||
|
||||
const product = await getAccessibleProduct(id, session.userId)
|
||||
if (!product) notFound()
|
||||
|
||||
const sprint = await prisma.sprint.findFirst({
|
||||
where: { product_id: id, status: 'ACTIVE' },
|
||||
})
|
||||
|
||||
if (!sprint) {
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<NoActiveSprint productId={id} productName={product.name} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const [rawTasks, rawUnassigned] = await Promise.all([
|
||||
prisma.task.findMany({
|
||||
where: {
|
||||
story: {
|
||||
sprint_id: sprint.id,
|
||||
assignee_id: session.userId,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
story: {
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
title: true,
|
||||
tasks: { select: { id: true }, orderBy: { sort_order: 'asc' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: [
|
||||
{ story: { pbi: { priority: 'asc' } } },
|
||||
{ story: { pbi: { sort_order: 'asc' } } },
|
||||
{ story: { sort_order: 'asc' } },
|
||||
{ priority: 'asc' },
|
||||
{ sort_order: 'asc' },
|
||||
],
|
||||
}),
|
||||
prisma.story.findMany({
|
||||
where: { sprint_id: sprint.id, assignee_id: null },
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
title: true,
|
||||
tasks: {
|
||||
select: { id: true, title: true, description: true, priority: true, status: true },
|
||||
orderBy: [{ priority: 'asc' }, { sort_order: 'asc' }],
|
||||
},
|
||||
},
|
||||
orderBy: { sort_order: 'asc' },
|
||||
}),
|
||||
])
|
||||
|
||||
const tasks: SoloTask[] = rawTasks.map(t => ({
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
description: t.description,
|
||||
implementation_plan: t.implementation_plan,
|
||||
priority: t.priority,
|
||||
sort_order: t.sort_order,
|
||||
status: t.status as SoloTask['status'],
|
||||
verify_only: t.verify_only,
|
||||
verify_required: t.verify_required as SoloTask['verify_required'],
|
||||
story_id: t.story.id,
|
||||
story_code: t.story.code,
|
||||
story_title: t.story.title,
|
||||
task_code: t.code,
|
||||
}))
|
||||
|
||||
const unassignedStories: UnassignedStory[] = rawUnassigned.map(s => ({
|
||||
id: s.id,
|
||||
code: s.code,
|
||||
title: s.title,
|
||||
tasks: s.tasks.map(t => ({
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
description: t.description,
|
||||
priority: t.priority,
|
||||
status: t.status,
|
||||
})),
|
||||
}))
|
||||
|
||||
return (
|
||||
<SoloBoard
|
||||
productId={id}
|
||||
sprintGoal={sprint.sprint_goal}
|
||||
tasks={tasks}
|
||||
unassignedStories={unassignedStories}
|
||||
isDemo={session.isDemo ?? false}
|
||||
currentUserId={session.userId}
|
||||
repoUrl={product.repo_url}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue